Une classe devrait-elle connaître ses sous-classes?


24

Une classe devrait-elle connaître ses sous-classes? Une classe doit-elle faire quelque chose de spécifique pour une sous-classe donnée par exemple?

Mon instinct me dit que c'est une mauvaise conception, cela ressemble à un anti-modèle en quelque sorte.


6
Généralement non, mais dans des cas particuliers, c'est utile.
CodesInChaos

Il est un anti-modèle, bien connu celui qui est appelé: « Liskov principe de substitution » ou parfois simplement LSP, lire à ce sujet à en.wikipedia.org/wiki/Liskov_substitution_principle
Jimmy Hoffa

5
@JimmyHoffa - "Liskov Substitution Principle" n'est pas le nom d'un anti-motif. Un anti-motif peut violer LSP, mais il n'est même pas certain que ce soit le cas ici. Il est possible que, même si un type sait qu'il s'agit de sous-types, les sous-types peuvent toujours être substitués à leur parent par la contravariance et la covariance.
Matthew Flynn

4
@MatthewFlynn Peut-être que j'utilise un peu le LSP mais dans ma définition, ce n'est pas le fait qu'ils remplissent tous les exigences les uns des autres qui est conforme au LSP, c'est une exigence qu'ils aient un découplage de telle sorte que non seulement ils sont d'accord avec l'autre, mais vous pouvez implémenter une autre sous-classe qui ne viole pas LSP. Si la hiérarchie est consciente d'elle-même, une implémentation externe ne pourrait pas répondre à LSP sans modifier la classe de base. Ce n'est peut-être pas strictement LSP mais c'est très proche. et oui j'aurais dû dire que la violation du LSP est l'anti-pattern
Jimmy Hoffa

2
Les quelques fois où j'ai rencontré cela, j'ai refactorisé cette connaissance dans une méthode qui pourrait être surchargée dans une sous-classe (forcée en la rendant abstraite ou pure virtuelle, ou en fournissant une implémentation par défaut sensible qui pourrait être surchargée).

Réponses:


32

La réponse impliquée par le concept de classes est "non".

Quelle que soit l'action, les données ou la relation que vous gérez fait partie de toutes les sous-classes - alors elles doivent être gérées dans la superclasse sans vérifier le type réel. Ou cela ne s'applique qu'à certaines sous-classes - alors vous devriez effectuer des vérifications de type au moment de l'exécution pour faire la bonne chose, la superclasse devrait être changée chaque fois que quelqu'un d'autre en hérite (ou elle pourrait silencieusement faire la mauvaise chose), les changements dans les classes dérivées peuvent casser la superclasse inchangée, etc.

En bref, vous obtenez un certain nombre de conséquences néfastes qui sont généralement suffisamment graves pour rejeter de telles solutions d'emblée. Si plusieurs de vos sous-classes font la même chose et que vous souhaitez éviter la duplication de code (pratiquement toujours une bonne chose), une meilleure solution consiste à introduire une classe de niveau intermédiaire à partir de laquelle toutes ces sous-classes peuvent hériter du code.


4
Une exception à cette règle: la documentation de la classe doit répertorier ses sous-classes locales dans sa propre bibliothèque.
Kieveli

3
@Kieveli ... tant que cette documentation est mise à jour.
mouviciel

14
Tout outil de documentation utilisable devrait pouvoir dessiner des arbres d'héritage ...
johannes

13
Je ne pense pas que ce soit une exception. La classe ne sait toujours rien. La documentation le sait.
Honza Brabec

4
@Kieveli euh je suis complètement en désaccord.
Jimmy Hoffa

16

Non seulement il ne devrait pas savoir, mais il ne peut tout simplement pas ! Habituellement, une classe peut être étendue à tout moment et en tout lieu. Il peut être étendu par des classes qui n'existaient même pas lors de son écriture.

Certaines langues permettent aux classes d'extension d'être contrôlées par la superclasse. Dans Scala, une classe peut être marquée comme sealed, ce qui signifie qu'elle ne peut être étendue que par d'autres classes au sein de la même unité de compilation (fichier source). Cependant, à moins que ces sous-classes ne soient également sealed ou final, les sous-classes peuvent ensuite être étendues par d'autres classes.

Dans Scala, ceci est utilisé pour modéliser des types de données algébriques fermés, donc le Listtype canonique de Haskell :

data List a = Nil | Cons a (List a)

peut être modélisé dans Scala comme ceci:

sealed trait List[+A]

case object Nil extends List[Nothing]

final case class Cons[+A] extends List[A]

Et vous pouvez garantir que seulement ces deux exist « classes » sous - car Listest sealedet ne peut donc pas être étendue en dehors du fichier, Consest finalet ne peut donc pas être étendue à tous et Nilest un objectqui ne peut être prolongé de toute façon.

Mais il s'agit d'un cas d'utilisation spécifique (modélisation de types de données algébriques via l'héritage) et même dans ce cas, la superclasse ne connaît pas réellement ses sous-classes. C'est plus une garantie pour l' utilisateur du Listtype que s'il fait une discrimination de cas Nilet Cons, il n'y aura pas d' autre alternative surgissant derrière son dos.


Ce qui fonctionne dans de nombreuses langues, c'est d'ajouter une forme de abstract internalmembre.
CodesInChaos

4

La réponse simple est non.

Il rend le code fragile et annule deux principes de base de la programmation orientée objet.

  • Principe de substitution de Liskov
  • Principe d'Open Close

1

Oui, parfois. Par exemple, lorsqu'il existe un nombre limité de sous-classes. Un modèle de visiteur est une illustration de l'utilité de cette approche.

Exemple: un nœud de syntaxe abstraite (AST) d'une grammaire bien définie peut tous être hérités d'une seule Nodeclasse implémentant un modèle de visiteur pour gérer tous les types de nœuds.


9
Non, non et non. Ce n'est pas utile et ce n'est jamais une bonne solution.
Sulthan

@Sulthan J'ai travaillé avec des AST en dehors de la salle de classe, et je pense que Lorus ne comprend pas vraiment la question. Un visiteur n'est pas un type de nœud sur un AST, c'est un objet séparé. Il peut et doit connaître les objets de grammaire. Mais un nœud AST de haut niveau ne devrait pas.

1
@JohnGaughan Le terme "sait" m'embrouille. La Nodeclasse de base , bien sûr, ne contient aucune référence directe à ses sous-classes. Mais il contient une acceptméthode avec un Visitorparamètre. Et Visitorcontient une visitméthode pour chaque sous-classe. Ainsi, bien qu'il Noden'ait aucune référence directe à ses sous-classes, il les "connaît" indirectement, via l' Visitorinterface. Ils se sont tous couplés à travers elle.
lorus

1
@Sulthan Ne dites jamais jamais. Le type d'option est un exemple valide du cas où la super-classe connaît la descendance. Mais comme Jorg l'a dit, il serait marqué comme scellé.
Pavel Voronin

Acceptez ensuite: un visiteur doit savoir ce qu'il visite.

1

Si j'écris un composant pour une entreprise et plus tard, après mon départ, quelqu'un le prolonge à ses propres fins dois-je en être informé?

Non!

Même chose avec les cours. Faites confiance à votre instinct.


La sécurité d'emploi???
sixtyfootersdude

Vos soixante pieds de haut, donc je ne discuterai pas avec vous :-) Cependant, j'espère que ma signification était après que je sois allé chercher un autre travail.
Daniel Hollinrake
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.