Est-il correct pour une classe d'utiliser sa propre méthode publique?

23

Contexte

J'ai actuellement une situation où j'ai un objet qui est à la fois transmis et reçu par un appareil. Ce message a plusieurs constructions, comme suit:

public void ReverseData()
public void ScheduleTransmission()

La ScheduleTransmissionméthode doit appeler la ReverseDataméthode chaque fois qu'elle est appelée. Cependant, il y a des moments où je ReverseDatadevrai appeler en externe (et je devrais ajouter entièrement en dehors de l'espace de noms ) d'où l'objet est instancié dans l'application.

Quant au "recevoir", je veux dire qu'il ReverseDatasera appelé en externe dans un object_receivedgestionnaire d'événements pour annuler les données.

Question

Est-il généralement acceptable qu'un objet appelle ses propres méthodes publiques?

Fouiner
la source
5
Il n'y a rien de mal à appeler une méthode publique par elle-même. Mais, d'après les noms de vos méthodes, ReverseData () me semble un peu dangereux d'être une méthode publique si elle inverse les données internes. Que faire si ReverseData () appelé en dehors de l'objet, puis appelé à nouveau avec ScheduleTransmission ().
Kaan
@Kaan Ce ne sont pas les vrais noms de mes méthodes, mais étroitement liés. En fait, les «données inversées» n'inversent que 8 bits du mot total et sont effectuées lorsque nous recevons et transmettons.
Snoop
La question se pose toujours. Que se passe-t-il si ces 8 bits sont inversés deux fois avant la planification de la transmission? Cela ressemble à un énorme trou dans l'interface publique. Penser à l'interface publique comme je le mentionne dans ma réponse pourrait aider à mettre ce problème en lumière.
Daniel T.
2
@StevieV Je crois que cela ne fait qu'amplifier l'inquiétude soulevée par Kaan. Il semble que vous exposiez une méthode qui modifie l'état de l'objet, et l'état dépend principalement du nombre d'appels de la méthode. Cela fait un cauchemar en essayant de garder une trace de l'état de l'objet tout au long de votre code. Il me semble plus que vous bénéficieriez de types de données distincts de ces états conceptuels, de sorte que vous pouvez dire de quoi il s'agit dans un morceau de code donné sans avoir à vous en préoccuper.
jpmc26
2
@StevieV quelque chose comme Data et SerializedData. Les données sont ce que le code voit et SerializedData est ce qui est envoyé via le réseau. Ensuite, transformez les données inverses (ou plutôt sérialisez / désérialisez les données) d'un type à l'autre.
csiz

Réponses:

33

Je dirais que ce n'est pas seulement acceptable, mais encouragé, surtout si vous prévoyez d'autoriser des extensions. Afin de prendre en charge les extensions de la classe en C #, vous devez marquer la méthode comme virtuelle selon les commentaires ci-dessous. Vous voudrez peut-être documenter cela, cependant, afin que quelqu'un ne soit pas surpris lorsque le remplacement de ReverseData () modifie le fonctionnement de ScheduleTransmission ().

Cela se résume vraiment à la conception de la classe. ReverseData () ressemble à un comportement fondamental de votre classe. Si vous devez utiliser ce comportement à d'autres endroits, vous ne voudrez probablement pas en avoir d'autres versions. Vous devez juste faire attention à ne pas laisser les détails spécifiques à ScheduleTransmission () s'infiltrer dans ReverseData (). Cela va créer des problèmes. Mais puisque vous l'utilisez déjà en dehors de la classe, vous y avez probablement déjà pensé.

JimmyJames
la source
Wow, ça me semblait vraiment bizarre mais ça aide. J'aimerais aussi voir ce que disent d'autres personnes. Merci.
Snoop
2
"de sorte que quelqu'un ne soit pas surpris en écrasant ReverseData () change le fonctionnement de ScheduleTransmission ()" : non, pas vraiment. Du moins pas en C # (notez que la question a une balise C #). À moins qu'il ne ReverseData()soit abstrait, peu importe ce que vous faites dans la classe enfant, c'est toujours la méthode originale qui sera appelée.
Arseni Mourzenko
2
@MainMa Or virtual... Et si vous remplacez la méthode, alors par définition, elle doit être virtualou abstract.
Pokechu22
1
@ Pokechu22: en effet, ça virtualmarche aussi. Dans tous les cas, la signature, selon OP, est public void ReverseData(), donc la partie de la réponse sur les choses primordiales est légèrement trompeuse.
Arseni Mourzenko
@MainMa vous dites que si une méthode de classe de base appelle une méthode virtuelle, elle ne se comporte pas de la manière virtuelle habituelle à moins qu'elle ne le soit abstract? J'ai recherché des réponses sur abstractvs virtualet je n'ai pas vu cela mentionné: plutôt abstrait signifie simplement qu'il n'y a pas de version donnée dans cette base.
JDługosz
20

Absolument.

La visibilité d'une méthode a pour seul but d'autoriser ou de refuser l'accès à une méthode en dehors de la classe ou au sein d'une classe enfant; Les méthodes publiques, protégées et privées peuvent toujours être appelées à l'intérieur de la classe elle-même.

Il n'y a rien de mal à appeler des méthodes publiques. L'illustration de votre question est l'exemple parfait d'une situation où avoir deux méthodes publiques est exactement ce que vous devez faire.

Cependant, vous pouvez surveiller attentivement les modèles suivants:

  1. Une méthode Hello()appelle World(string, int, int)comme ceci:

    Hello()
    {
        this.World("Some magic value here", 0, 100);
    }

    Évitez ce schéma. Utilisez plutôt des arguments facultatifs . Les arguments facultatifs facilitent la découverte: l'appelant qui tape Hello(ne sait pas nécessairement qu'il existe une méthode qui permet d'appeler la méthode avec des valeurs par défaut. Les arguments facultatifs sont également auto-documentés. World()ne montre pas à l'appelant quelles sont les valeurs par défaut réelles.

  2. Une méthode Hello(ComplexEntity)appelle World(string, int, int)comme ceci:

    Hello(ComplexEntity entity)
    {
        this.World(entity.Name, entity.Start, entity.Finish);
    }

    Utilisez plutôt des surcharges . Même raison: meilleure découvrabilité. L'appelant peut voir à la fois toutes les surcharges via IntelliSense et choisir la bonne.

  3. Une méthode appelle simplement d'autres méthodes publiques sans ajouter de valeur substantielle.

    Réfléchissez bien. Avez-vous vraiment besoin de cette méthode? Ou devez-vous le supprimer et laisser l'appelant invoquer les différentes méthodes? Un conseil: si le nom de la méthode ne semble pas correct ou est difficile à trouver, vous devriez probablement le supprimer.

  4. Une méthode valide l'entrée et appelle ensuite l'autre méthode:

    Hello(string name, int start, int end)
    {
        if (name == null) throw new ArgumentNullException(...);
        if (start < 0) throw new OutOfRangeException(...);
        ...
        if (end < start) throw new ArgumentException(...);
    
        this.World(name, start, end);
    }

    Au lieu de cela, Worlddevrait valider ses paramètres lui-même ou être privé.

Arseni Mourzenko
la source
Les mêmes idées s'appliqueraient-elles si ReverseData était réellement interne et utilisé dans tout l'espace de noms contenant des instances d '"objet"?
Snoop
1
@StevieV: oui, bien sûr.
Arseni Mourzenko
@MainMa Je ne comprends pas quelle est la raison derrière les points 1 et 2
Kapol
@Kapol: merci pour vos commentaires. J'ai modifié ma réponse en ajoutant des explications sur les deux premiers points.
Arseni Mourzenko
Même dans le cas 1, je préfère généralement les surcharges plutôt que les arguments facultatifs. Si les options sont telles que l'utilisation de surcharges n'est pas une option ou produit un grand nombre de surcharges, je créerais un type personnalisé et l'utiliserais comme argument (comme dans la suggestion 2), ou refactoriser le code.
Brian
8

Si quelque chose est public, il peut être appelé à tout moment par n'importe quel système. Il n'y a aucune raison pour que vous ne puissiez pas être l'un de ces systèmes aussi!

Dans les bibliothèques hautement optimisées, vous souhaitez copier l'idiome mis en avant java.util.ArrayList#ensureCapacity(int).

assurerCapacité

  • ensureCapacity est public
  • ensureCapacity a toutes les limites nécessaires de vérification et de valeurs par défaut, etc.
  • ensureCapacity appels ensureExplicitCapacity

assureCapacitéInterne

  • ensureCapacityInternal est privé
  • ensureCapacityInternal a une vérification d'erreur minimale car toutes les entrées proviennent de l'intérieur de la classe
  • ensureCapacityInternal Appelle AUSSI ensureExplicitCapacity

assureExplicitCapacity

  • ensureExplicitCapacity est AUSSI privé
  • ensureExplicitCapacityn'a pas de vérification d'erreur
  • ensureExplicitCapacity fait du vrai travail
  • ensureExplicitCapacityn'est appelé de nulle part sauf par ensureCapacityetensureCapacityInternal

De cette façon, le code en lequel vous avez confiance obtient un accès privilégié (et plus rapide!) Car vous savez que ses entrées sont bonnes. Le code auquel vous ne faites pas confiance passe par une certaine rigueur pour vérifier son intégrité et bombarder ou fournir des valeurs par défaut ou gérer autrement de mauvaises entrées. Tous deux se dirigent vers l'endroit qui fonctionne réellement.

Cependant, cela est utilisé dans ArrayList, l'une des classes les plus utilisées dans le JDK. Il est très possible que votre cas ne nécessite pas ce niveau de complexité et de rigueur. Pour faire court, si tous les ensureCapacityInternalappels étaient remplacés par des ensureCapacityappels, les performances seraient toujours vraiment, vraiment bonnes. Il s'agit d'une microoptimisation qui n'a probablement été réalisée qu'après mûre réflexion.

corsiKa
la source
3

Plusieurs bonnes réponses ont déjà été données, et je suis également d'accord avec elles sur le fait qu'un objet peut appeler ses méthodes publiques à partir de ses autres méthodes. Cependant, il y a une légère mise en garde de conception que vous devrez surveiller.

Les méthodes publiques ont généralement un contrat de "prendre l'objet dans un état cohérent, faire quelque chose de sensé, laisser l'objet dans un état cohérent (éventuellement différent)". Ici, «cohérent» peut signifier, par exemple, que le Lengthde a List<T>n'est pas supérieur au sien Capacityet que référencer les éléments aux indices de 0à Length-1ne lancera pas.

Mais à l'intérieur des méthodes de l'objet, l'objet peut être dans un état incohérent, donc lorsque vous appelez l'une de vos méthodes publiques, cela peut faire une très mauvaise chose, car il n'a pas été écrit avec une telle possibilité à l'esprit. Donc, si vous prévoyez d'appeler vos méthodes publiques à partir de vos autres méthodes, assurez-vous que leur contrat est "prenez l'objet dans un état, faites quelque chose de sensé, laissez l'objet dans un (peut-être différent) (peut-être incohérent - mais seulement si l'initiale état incohérent) ".

Joker_vD
la source
1

Un autre exemple lorsque l'appel d'une méthode publique à l'intérieur d'une autre méthode publique est tout à fait correct est une approche CanExecute / Execute. Je l'utilise lorsque j'ai besoin à la fois d'une validation et d'une conservation invariante .

Mais en général, je suis toujours prudent à ce sujet. Si une méthode a()est appelée méthode inside b(), cela signifie qu'elle a()est un détail d'implémentation de b(). Très souvent, cela indique qu'ils appartiennent à différents niveaux d'abstraction. Et le fait qu'ils soient tous les deux publics me fait me demander s'il s'agit ou non d'une violation du principe de responsabilité unique .

Zapadlo
la source
-5

Désolé mais je vais devoir être en désaccord avec la plupart des autres réponses «oui vous pouvez» et dire que:

Je découragerais une classe d'appeler une méthode publique d'une autre

Il y a quelques problèmes potentiels avec cette pratique.

1: boucle infinie dans la classe héritée

Ainsi, votre classe de base appelle method1 de method2 mais vous ou quelqu'un d'autre en héritez et cache method1 avec une nouvelle méthode qui appelle method2.

2: événements, journalisation, etc.

par exemple, j'ai une méthode Add1 qui déclenche un événement '1 ajouté!' Je ne veux probablement pas que la méthode Add10 déclenche cet événement, écrive dans un journal ou autre, dix fois.

3: filetage et autres blocages

Par exemple, InsertComplexData ouvre une connexion db, démarre une transaction, verrouille une table, puis appelle InsertSimpleData, avec ouvre une connexion, démarre une transaction, attend que la table soit déverrouillée ....

Je suis sûr qu'il y a plus de raisons, l'une des autres réponses a abordé `` vous modifiez la méthode1 et vous êtes surpris que la méthode2 commence à se comporter différemment ''

En général, si vous avez deux méthodes publiques qui partagent du code, il vaut mieux les faire appeler toutes les deux une méthode privée plutôt que l'une d'appeler l'autre.

Modifier ----

Permet d'étendre le cas spécifique du PO.

nous n'avons pas beaucoup de détails mais nous savons que ReverseData est appelé par un gestionnaire d'événements d'une certaine sorte ainsi que par la méthode ScheduleTransmission.

Je suppose que les données inversées modifient également l'état interne de l'objet

Dans ce cas, je pense que la sécurité des fils serait importante et c'est pourquoi ma troisième objection à la pratique s'applique.

Pour sécuriser le thread ReverseData, vous pouvez ajouter un verrou. Mais si ScheduleTransmission doit également être sûr pour les threads, vous souhaiterez partager le même verrou.

La façon la plus simple de procéder consiste à déplacer le code ReverseData dans une méthode privée et à ce que les deux méthodes publiques l'appellent. Vous pouvez ensuite placer l'instruction lock dans les méthodes publiques et partager un objet lock.

De toute évidence, vous pouvez dire "cela n'arrivera jamais!" ou "Je pourrais programmer la serrure d'une autre manière" mais le point essentiel d'une bonne pratique de codage est de bien structurer votre code en premier lieu.

En termes académiques, je dirais que cela viole le L en solide. Les méthodes publiques sont plus que simplement accessibles au public. Ils sont également modifiables par ses héritiers. Votre code doit être fermé pour modification, ce qui signifie que vous devez réfléchir à votre travail dans les méthodes publiques et protégées.

En voici un autre: vous violez également potentiellement DDD. Si votre objet est un objet de domaine, ses méthodes publiques doivent être des termes de domaine qui signifient quelque chose pour l'entreprise. Dans ce cas, il est très peu probable que «acheter une douzaine d'œufs» soit la même chose que «acheter 1 œuf 12 fois» même si cela commence de cette façon.

Ewan
la source
3
Ces problèmes ressemblent plus à une architecture mal pensée qu'à une condamnation générale du principe de conception. (Peut-être qu'appeler "1 ajouté" est bien à chaque fois. Si ce n'est pas le cas, nous devrions programmer différemment. Peut-être que si deux méthodes tentent de verrouiller de manière exclusive la même ressource, elles ne devraient pas s'appeler l'une l'autre, ou nous les avons mal écrites .) Plutôt que de présenter de mauvaises implémentations, vous voudrez peut-être vous concentrer sur des arguments plus solides qui abordent l'accès aux méthodes publiques en tant que principe universel. Par exemple, étant donné un bon code soigné, pourquoi est-il mauvais?
doppelgreener
Exactement, si ce n'est pas le cas, vous n'avez pas de place pour organiser l'événement. De même avec # 3, tout va bien jusqu'à ce qu'une de vos méthodes en bas de la chaîne ait besoin d'une transaction, alors vous êtes foutu
Ewan
Tous ces problèmes reflètent davantage la mauvaise qualité du code lui-même que le principe général défectueux. Les trois cas ne sont que du mauvais code. Ils peuvent être résolus par une version de « ne pas faire ça, faire à la place » (peut - être demander « ce ne voulez - vous exactement? »), Et ne remettent pas en cause le principe général d'une classe appelant son propre public méthodes. Le vague potentiel d'une boucle infinie est le seul ici qui aborde la question par principe, et même alors, il n'est pas abordé de manière approfondie.
doppelgreener
le seul thibg le «code» dans mes partages d'exemple est une méthode publique en appelant une autre. Si vous pensez que son «mauvais code» est bon! C'est mon affirmation
Ewan
Que vous puissiez utiliser un marteau pour vous casser la main ne signifie pas que le marteau est mauvais. Cela signifie que vous ne devez pas faire avec lui des choses qui vous briseraient la main.
doppelgreener