D'où vient ce concept de «composition privilégiée par rapport à l'héritage»?


143

Au cours des derniers mois, le mantra "favoriser la composition par rapport à l'héritage" semble être né de nulle part et est devenu presque une sorte de mème au sein de la communauté de programmation. Et chaque fois que je le vois, je suis un peu mystifié. C'est comme si quelqu'un disait "privilégie les marteaux". D'après mon expérience, la composition et l'héritage sont deux outils différents avec des cas d'utilisation différents, et les traiter comme s'ils étaient interchangeables et que l'un était intrinsèquement supérieur à l'autre n'a aucun sens.

En outre, je ne vois jamais une explication réelle de la raison pour laquelle l' héritage est mauvais et la composition bonne, ce qui me rend encore plus méfiant. Est-il censé être accepté sur la foi? La substitution et le polymorphisme de Liskov ont des avantages bien connus et bien définis, et l’OMI comprend tout l’intérêt d’utiliser une programmation orientée objet, et personne n’explique jamais pourquoi ils devraient être écartés au profit de la composition.

Quelqu'un sait-il d'où vient ce concept et quelle en est la raison?


45
Comme d'autres l'ont souligné, cela fait longtemps que je ne suis pas surpris. Je suis surpris que vous en entendiez parler maintenant. Il est intuitif pour quiconque a construit de grands systèmes dans des langages comme Java, peu importe la durée. C'est le cœur de toute entrevue que je donne et lorsqu'un candidat commence à parler d'héritage, je commence à douter de son niveau de compétence et de son expérience. Voici une bonne introduction à la raison pour laquelle l'héritage est une solution fragile (il y en a beaucoup d'autres): artima.com/lejava/articles/designprinciples4.html
Janx

6
@Janx: Peut-être que c'est ça. Je ne construis pas de gros systèmes dans des langages comme Java; Je les construis à Delphi, et sans substitution de Liskov et polymorphisme, nous ne pourrions jamais rien faire. Son modèle objet diffère à certains égards de celui de Java ou de C ++, et bon nombre des problèmes que cette maxime semble avoir pour but de résoudre n'existent pas vraiment, ou sont beaucoup moins problématiques, dans Delphi. Différentes perspectives de différents points de vue, je suppose.
Mason Wheeler

14
J'ai passé plusieurs années au sein d'une équipe chargée de la construction de systèmes relativement volumineux à Delphes et de grands arbres d'héritage ont certainement mordu notre équipe et nous ont causé une douleur considérable. Je soupçonne que votre attention aux principes SOLID vous aide à éviter les problèmes, pas votre utilisation de Delphi.
Bevan

8
Derniers mois?!?
Jas

6
IMHO, ce concept n'a jamais été totalement adapté à la variété de langages prenant en charge l'héritage d'interface (c'est-à-dire, le sous-typage avec des interfaces pures) et l'héritage d'implémentation. Trop de gens suivent ce mantra et n'utilisent pas assez d'interfaces.
Uri

Réponses:


136

Bien que je pense avoir entendu des discussions composition-héritage bien avant le GoF, je ne peux pas mettre le doigt sur une source spécifique. Cela aurait pu être Booch de toute façon.

<rant>

Ah mais comme beaucoup de mantras, celui-ci a dégénéré selon des lignes typiques:

  1. il est présenté avec une explication détaillée et les arguments d'une source très respectée qui utilise la phrase-clé pour rappeler la discussion complexe d'origine
  2. il est partagé avec un clin d'œil au club par quelques connaisseurs pendant un certain temps, généralement lorsqu'il commente les erreurs de n00b
  3. Bientôt, des milliers de milliers de personnes le répètent sans réfléchir , mais ne l'utilisent jamais, mais adorent s'en servir comme prétexte pour ne pas penser et comme moyen simple et peu coûteux de se sentir supérieur aux autres
  4. en fin de compte, aucune déprédation raisonnable ne peut endiguer la "marée" - et le paradigme dégénère en religion et en dogme.

Le meme, destiné à l'origine à amener l'ampoule à l'illumination, est maintenant utilisé comme un club pour matraquer leur inconscient.

La composition et l'héritage sont des choses très différentes et ne doivent pas être confondues. S'il est vrai que la composition peut être utilisée pour simuler l'héritage avec beaucoup de travail supplémentaire , cela ne fait pas de l'héritage un citoyen de seconde classe, ni ne fait de la composition le fils préféré. Le fait que beaucoup de n00bs essaient d'utiliser l'héritage comme raccourci n'invalide pas le mécanisme, et presque tous n00bs tirent les leçons de l'erreur et s'améliorent de ce fait.

S'il vous plaît PENSEZ à vos conceptions, et d' arrêter des slogans jaillissant.

</ rant>


16
Booch soutient que l'héritage d'implémentation introduit un couplage élevé entre les classes. D'autre part, il considère l'héritage comme le concept clé qui distingue la programmation orientée objet (OOP) de la programmation procédurale.
Nemanja Trifunovic

13
Il devrait y avoir un mot pour ce genre de chose. Une régurgitation prématurée , peut-être?
Jörg W Mittag

8
@ Jörg: Vous savez ce qui va arriver à un terme comme Regurgitation précoce? Exactement ce qui est expliqué ci-dessus. :) (quand la régurgitation n'est-elle jamais prématurée?)
Bjarke Freund-Hansen

6
@Nemanja: D'accord. La question est de savoir si ce couplage est vraiment mauvais. Si les classes sont conceptuellement fortement couplées et forment conceptuellement une relation supertype-sous-type, et qu'elles ne peuvent jamais être découplées de manière concue même si elles sont formellement découplées au niveau de la langue, le couplage fort convient parfaitement.
Dsimcha

5
Le mantra est simple: "ce n'est pas parce que vous pouvez donner à n'importe quel jeu de jouer une apparence que cela ne signifie que vous devez tout faire de ce jeu-là." ;)
Evan Plaice le

73

Expérience.

Comme vous le dites, ce sont des outils pour différents emplois, mais l'expression a été créée parce que les gens ne l'utilisaient pas de cette façon.

L'héritage est avant tout un outil polymorphe, mais certaines personnes, à leurs risques et périls par la suite, tentent de l'utiliser comme moyen de réutiliser / partager du code. Le raisonnement étant "bien si j'hérite, j'obtiens toutes les méthodes gratuitement", mais en ignorant le fait que ces deux classes n'ont potentiellement aucune relation polymorphe.

Alors, pourquoi privilégier la composition à l’héritage - enfin, tout simplement parce que le plus souvent, la relation entre les classes n’est pas polymorphe. Il existe simplement pour aider à rappeler aux gens de ne pas réagir par héritage.


7
Donc, fondamentalement, vous ne pouvez pas dire "vous ne devriez pas utiliser la POO en premier lieu si vous ne comprenez pas la substitution de Liskov", vous dites donc "favoriser la composition par rapport à l'héritage" au lieu de tenter de limiter les dommages causés par un incompétent codeurs?
Mason Wheeler

13
@Mason: Comme n'importe quel mantra, "privilégier la composition à l'héritage" est destiné aux programmeurs débutants. Si vous savez déjà quand utiliser l'héritage et quand utiliser la composition, il est inutile de répéter un mantra comme celui-là.
Dean Harding

7
@ Dean - Je ne suis pas sûr que débutant soit le même que celui qui ne comprend pas les meilleures pratiques en matière d'héritage. Je pense qu'il y a plus que ça. Un mauvais héritage est la cause de nombreux maux de tête dans mon travail et ce n'est pas du code écrit par des programmeurs qui seraient considérés comme des "débutants".
Nicole

6
@Renesis: La première chose à laquelle je pense en entendant cela est que "certaines personnes ont dix ans d'expérience et d'autres une expérience répétée dix fois".
Mason Wheeler

8
Je concevais un projet assez volumineux juste après le premier "Obtention" de OO et comptais beaucoup sur lui. Même si cela a bien fonctionné, j’ai trouvé que le design était toujours fragile, provoquant des irritations mineures ici et là et faisant parfois de certains refactors une vraie chienne. Il est un peu difficile d'expliquer toute l'expérience, mais l'expression "Favoriser la composition par rapport à l'héritage" la décrit EXACTEMENT. Notez que cela ne dit pas ou même n'implique pas "Évitez l'héritage", cela vous donne simplement un petit coup de pouce quand le choix n'est pas évident.
Bill K

43

Ce n'est pas une idée nouvelle, je pense qu'elle a en fait été introduite dans le livre sur les modèles de conception du GoF, publié en 1994.

Le principal problème de l'héritage est qu'il s'agit d'une boîte blanche. Par définition, vous devez connaître les détails d'implémentation de la classe dont vous héritez. Avec la composition, par contre, vous ne vous préoccupez que de l'interface publique de la classe que vous composez.

Du livre GoF:

L'héritage expose une sous-classe aux détails de la mise en œuvre de son parent, il est souvent dit que "l'héritage rompt l'encapsulation"

L'article de Wikipédia sur le livre de GoF a une introduction décente.


14
Je ne suis pas d'accord avec ça. Vous n'avez pas besoin de connaître les détails d'implémentation de la classe dont vous héritez; seuls les membres publics et protégés exposés par la classe. Si vous devez connaître les détails de l'implémentation, alors soit vous, soit l'auteur de la classe de base, faites quelque chose de mal, et si la faille est dans la classe de base, la composition ne vous aidera pas à résoudre le problème.
Mason Wheeler

18
Comment pouvez-vous être en désaccord avec quelque chose que vous n'avez pas lu? Il y a une page et demie de discussion de GoF dont vous ne voyez qu'une infime vue.
philosodad

7
@pholosodad: Je ne suis pas en désaccord avec quelque chose que je n'ai pas lu. Je ne suis pas d'accord avec ce que Dean a écrit: "Par définition, vous devez connaître les détails de la mise en oeuvre de la classe dont vous héritez", ce que j'ai lu.
Mason Wheeler

10
Ce que j'ai écrit n'est qu'un résumé de ce qui est décrit dans le livre GoF. Je l'ai peut-être formulé de manière un peu forte (vous n'avez pas besoin de connaître tous les détails de la mise en œuvre), mais c'est la raison générale pour laquelle le gouvernement a préconisé la composition par rapport à l'héritage.
Dean Harding

2
Corrigez-moi si je me trompe, mais je le vois ainsi: "privilégiez les relations de classe explicites (c'est-à-dire les interfaces) par rapport à implicites (c'est-à-dire l'héritage)." Les premiers vous disent ce dont vous avez besoin sans vous dire comment le faire. Ce dernier vous indiquera non seulement comment le faire, mais vous le regrettera plus tard.
Evan Plaice le

26

Pour répondre à une partie de votre question, je pense que ce concept est apparu pour la première fois dans le livre GOF Design Patterns: Éléments d'un logiciel orienté objet réutilisable , publié pour la première fois en 1994. L'expression apparaît en haut de la page 20, dans l'introduction:

Favoriser la composition d'objets par rapport à l'héritage

Ils préfacent cette déclaration par une brève comparaison de l'héritage avec la composition. Ils ne disent pas "ne jamais utiliser l'héritage".


23

"Composition sur héritage" est un moyen court (et apparemment trompeur) de dire "Si vous pensez que les données (ou le comportement) d'une classe doivent être incorporés dans une autre classe, envisagez toujours d'utiliser la composition avant d'appliquer aveuglément l'héritage".

Pourquoi est-ce vrai? Parce que l'héritage crée un couplage étroit, au moment de la compilation, entre les 2 classes. La composition, en revanche, est un couplage lâche, qui permet notamment de séparer clairement les préoccupations, la possibilité de changer de dépendance au moment de l'exécution et de tester plus facilement et plus isolément la dépendance.

Cela signifie seulement que l'héritage doit être manipulé avec précaution, car cela a un coût et non pas que cela n'est pas utile. En fait, "Composition sur héritage" finit souvent par devenir "Composition + héritage sur héritage" puisque vous voulez souvent que votre dépendance composée soit une super-classe abstraite plutôt que la sous-classe concrète elle-même. Il vous permet de basculer entre différentes implémentations concrètes de votre dépendance lors de l'exécution.

Pour cette raison (parmi d'autres), vous verrez probablement que l'héritage est utilisé plus souvent sous la forme d'implémentation d'interface ou de classes abstraites que l'héritage vanille.

Un exemple (métaphorique) pourrait être:

"J'ai une classe Snake et je veux inclure dans cette classe ce qui se passe lorsque Snake mord. Je serais tenté de faire en sorte que Snake hérite d'une classe BiterAnimal qui utilise la méthode Bite () et remplace cette méthode pour refléter la morsure venimeuse. Mais Composition sur héritage me prévient que je devrais plutôt utiliser la composition ... Dans mon cas, cela pourrait se traduire par le fait que le serpent possède un membre Bite. La classe Bite pourrait être abstraite (ou une interface) avec plusieurs sous-classes. Par exemple, avoir des sous-classes VenomousBite et DryBite et être capable de changer de morsure sur le même exemple Snake à mesure que le serpent grandit, mais gérer tous les effets d’une Bite dans sa propre classe pourrait me permettre de le réutiliser dans cette classe Frost. , parce que le gel mord mais n’est pas un BiterAnimal, et ainsi de suite ... "


4
Très bon exemple avec le serpent. J'ai rencontré un cas similaire récemment avec mes propres cours
Maksee

22

Quelques arguments possibles pour la composition:

La composition est légèrement plus l'
héritage agnostique de langage / framework et ce qu'il impose / exige / permet différera entre les langages en termes de ce à quoi la sous-classe / superclasse a accès et quelles implications il peut avoir sur les méthodes virtuelles, etc. La composition est assez basique et nécessite très peu de prise en charge linguistique et, par conséquent, les implémentations sur différentes plates-formes / infrastructures peuvent partager plus facilement les modèles de composition.

La composition est un moyen très simple et tactile de construire des objets
L'héritage est relativement facile à comprendre, mais pas toujours aussi facilement démontré dans la vie réelle. De nombreux objets de la vie réelle peuvent être décomposés et composés. Supposons qu'un vélo puisse être construit en utilisant deux roues, un cadre, un siège, une chaîne, etc. Expliqué facilement par la composition. Tandis que dans une métaphore de l'héritage, on pourrait dire qu'une bicyclette étend un monocycle, ce qui est réalisable mais beaucoup plus éloigné de l'image réelle que la composition (ce n'est évidemment pas un très bon exemple d'héritage, mais le point reste le même). Même le mot héritage (du moins de la plupart des anglophones américains, je l’attendrais) invoque automatiquement une signification du type "Quelque chose d’un parent décédé" qui a une corrélation avec sa signification dans le logiciel, mais ne correspond que vaguement.

La composition est presque toujours plus flexible
En utilisant la composition, vous pouvez toujours choisir de définir votre propre comportement ou simplement d'exposer cette partie de vos parties composées. De cette manière, vous ne rencontrez aucune des restrictions pouvant être imposées par une hiérarchie d'héritage (virtuel ou non virtuel, etc.).

Donc, cela pourrait être parce que Composition est naturellement une métaphore plus simple qui a moins de contraintes théoriques que l'héritage. De plus, ces raisons particulières peuvent être plus apparentes lors de la conception, ou éventuellement se manifester lorsqu’il s’agit de résoudre certains des problèmes liés à l’héritage.

Clause de non-responsabilité:
Évidemment, ce n'est pas une rue à sens unique. Chaque conception mérite l’évaluation de plusieurs modèles / outils. L'héritage est largement utilisé, présente de nombreux avantages et est souvent plus élégant que la composition. Ce ne sont là que quelques-unes des raisons que l’on pourrait utiliser pour favoriser la composition.


1
"De toute évidence, ce n’est pas une rue à sens unique." Quand la composition n'est-elle pas plus (ou également) flexible que l'héritage? Je dirais que très bien est une rue à sens unique . L'héritage n'est qu'un sucre syntaxique pour un cas particulier de composition.
weberc2

La composition n'est souvent pas flexible lorsque vous utilisez un langage qui l'implémente à l'aide d' interfaces explicitement implémentées , car ces interfaces ne sont pas autorisées à évoluer de manière compatible avec les versions antérieures dans le temps. #jmtcw
MikeSchinkel

11

Vous venez peut-être de remarquer que des gens l'ont dit ces derniers mois, mais les bons programmeurs le savent depuis plus longtemps. Je le dis certainement, le cas échéant, depuis environ dix ans.

L'idée du concept est qu'il y a une lourde charge conceptuelle sur l'héritage. Lorsque vous utilisez l'héritage, chaque appel de méthode comporte une répartition implicite. Si vous avez des arbres d'héritage profonds, ou des envois multiples, ou (pire encore) les deux, déterminer à quel endroit la méthode particulière sera envoyée dans un appel donné peut devenir un PITA royal. Cela rend le raisonnement correct du code plus complexe et rend le débogage plus difficile.

Laissez-moi vous donner un exemple simple à illustrer. Supposons que, profondément dans un arbre d'héritage, quelqu'un ait nommé une méthode foo. Ensuite, quelqu'un d'autre arrive et ajoute fooau sommet de l'arbre, mais fait quelque chose de différent. (Ce cas est plus commun avec l'héritage multiple.) Maintenant, cette personne travaillant dans la classe racine a cassé la classe enfant obscure et ne s'en rend probablement pas compte. Vous pourriez avoir une couverture de 100% avec les tests unitaires et ne pas remarquer cette rupture car la personne située en haut ne penserait pas à tester la classe enfant, et les tests pour la classe enfant ne songent pas à tester les nouvelles méthodes créées en haut. . (Certes, il existe des moyens d'écrire des tests unitaires pour résoudre ce problème, mais il existe également des cas dans lesquels vous ne pouvez pas écrire facilement des tests de cette façon.)

En revanche, lorsque vous utilisez la composition, à chaque appel, le message auquel vous envoyez l'appel est généralement plus clair. (OK, si vous utilisez l'inversion de contrôle, par exemple avec l'injection de dépendance, il peut être problématique de savoir où l'appel va aller. Mais en général, c'est plus simple à comprendre.) Cela facilite le raisonnement. En prime, la composition a pour résultat que les méthodes sont séparées les unes des autres. L'exemple ci-dessus ne devrait pas s'y produire car la classe enfant passerait à un composant obscur et il n'y a jamais de question de savoir si l'appel à fooétait destiné au composant obscur ou à l'objet principal.

Maintenant, vous avez absolument raison de dire que l'héritage et la composition sont deux outils très différents qui servent deux types de choses différentes. Bien sûr, l'héritage entraîne une surcharge conceptuelle, mais lorsqu'il est le bon outil pour le travail, il entraîne moins de surcharge conceptuelle que d'essayer de ne pas l'utiliser et de faire à la main ce qu'il fait pour vous. Personne qui sait ce qu'ils font ne dirait qu'il ne faut jamais utiliser l'héritage. Mais assurez-vous que c'est la bonne chose à faire.

Malheureusement, de nombreux développeurs en apprennent davantage sur les logiciels orientés objet, sur l'héritage, puis utilisent leur nouvel axe aussi souvent que possible. Ce qui signifie qu'ils essaient d'utiliser l'héritage où la composition était le bon outil. Espérons qu'ils apprendront mieux avec le temps, mais souvent cela ne se produit qu'après quelques membres enlevés, etc. Leur dire dès le départ que c'est une mauvaise idée a tendance à accélérer le processus d'apprentissage et à réduire le nombre de blessures.


3
Très bien, je suppose que cela a du sens du point de vue C ++. C'est une chose à laquelle je n'avais jamais pensé, car ce n'est pas un problème dans Delphi, c'est ce que j'utilise le plus souvent. (Il n'y a pas d'héritage multiple, et si vous avez une méthode dans une classe de base et une autre méthode du même nom dans une classe dérivée qui ne substitue pas la méthode de base, le compilateur émettra un avertissement afin que vous n'aboutissiez pas accidentellement. avec ce genre de problème.)
Mason Wheeler

1
@Mason: La version d'Ander Pascal Objet (alias Delphi) est supérieure à C ++ et Java en ce qui concerne le problème de l'héritage de classe de base fragile. Contrairement à C ++ et Java, la surcharge d'une méthode virtuelle héritée n'est pas implicite.
peu twiddler

2
@ bit-twiddler, on peut dire ce que vous dites sur C ++ et Java sur Smalltalk, Perl, Python, Ruby, JavaScript, Common Lisp, Objective-C et tous les autres langages que j'ai appris qui fournissent une forme de support OO. Cela dit, googler suggère que C # suit l'exemple de Object Pascal.
mardi

3
En effet, Anders Hejlsberg a conçu la version Borland de Object Pascal (alias Delphi) et C #.
peu twiddler

8

C'est une réaction à l'observation selon laquelle les débutants OO ont tendance à utiliser l'héritage lorsqu'ils n'en ont pas besoin. L'héritage n'est certes pas une mauvaise chose, mais on peut en abuser. Si une classe a simplement besoin de fonctionnalités d'une autre, la composition fonctionnera probablement. Dans d'autres circonstances, l'héritage fonctionnera et la composition ne fonctionnera pas.

Hériter d'une classe implique beaucoup de choses. Cela implique qu’une dérivée est un type de base (voir le principe de substitution de Liskov pour les détails sanglants), en ce sens que chaque fois que vous utilisez une base, il est logique d’utiliser une dérivée. Il donne l'accès dérivé aux membres protégés et aux fonctions membres de Base. C'est une relation étroite, ce qui signifie qu'il a un couplage élevé, et les modifications apportées à l'une sont plus susceptibles de nécessiter des modifications à l'autre.

Le couplage est une mauvaise chose. Cela rend les programmes plus difficiles à comprendre et à modifier. Toutes choses égales par ailleurs, vous devez toujours sélectionner l'option avec moins de couplage.

Par conséquent, si la composition ou l'héritage remplit son rôle efficacement, choisissez la composition, car son couplage est faible. Si la composition ne fait pas le travail efficacement et que l'héritage le fera, choisissez l'héritage, car vous devez le faire.


"Dans d'autres circonstances, l'héritage fonctionnera et la composition, non." Quand? L'héritage peut être modélisé à l'aide de la composition et du polymorphisme d'interface. Je serais donc surpris de constater qu'il existe des circonstances dans lesquelles la composition ne fonctionnera pas.
weberc2

8

Voici mes deux cents (au-delà de tous les excellents points déjà soulevés):

IMHO, Cela revient au fait que la plupart des programmeurs n'obtiennent pas vraiment l'héritage et finissent par en abuser, en partie à cause de la manière dont ce concept est enseigné. Ce concept existe pour tenter de les dissuader de trop en faire, au lieu de leur apprendre à bien faire les choses.

Quiconque a consacré du temps à l'enseignement ou au mentorat sait que c'est souvent ce qui se passe, en particulier avec les nouveaux développeurs qui ont de l'expérience dans d'autres paradigmes:

Au début, ces développeurs ont le sentiment que l'héritage est ce concept effrayant. Ils finissent donc par créer des types avec des chevauchements d’interface (par exemple, le même comportement spécifié sans sous-typage commun), et avec des éléments globaux pour implémenter des fonctionnalités communes.

Ensuite (souvent à la suite d'un enseignement trop zélé), ils découvrent les avantages de l'héritage, mais c'est souvent enseigné comme la solution miracle à la réutilisation. Ils finissent par avoir la perception que tout comportement partagé doit être partagé par héritage. En effet, l'accent est souvent mis sur l'héritage d'implémentation plutôt que sur le sous-typage.

Dans 80% des cas, c'est assez bien. Mais les 20% restants sont ceux où le problème commence. Afin d'éviter de réécrire et de s'assurer qu'ils ont bien profité du partage de l'implémentation, ils commencent à concevoir leur hiérarchie autour de l'implémentation prévue plutôt que des abstractions sous-jacentes. Le "Stack hérite de la liste doublement chaînée" en est un bon exemple.

La plupart des enseignants n'insistent pas pour l'instant sur le concept d'interface, parce que c'est un autre concept ou parce que (en C ++), vous devez les simuler en utilisant des classes abstraites et un héritage multiple qui n'est pas enseigné à ce stade. En Java, de nombreux enseignants ne distinguent pas le "pas d'héritage multiple" ou "l'héritage multiple est diabolique" de l'importance des interfaces.

Tout cela est aggravé par le fait que nous avons tellement appris qu'il est magnifique de ne pas avoir à écrire de code superflu avec l'héritage d'implémentation. Des tonnes de code de délégation simple ne semblent pas naturelles. La composition semble donc compliquée, c'est pourquoi nous avons besoin de ces règles empiriques pour pousser les nouveaux programmeurs à les utiliser (ce qu'ils font trop).


C'est une vraie partie de l'éducation. Vous devez suivre les cours supérieurs pour obtenir le reste de 20%, mais vous, en tant qu'étudiant, venez juste d'enseigner les cours de base et peut-être intermédiaire. À la fin, vous vous sentez bien enseigné parce que vous avez bien suivi vos cours (et que vous ne le voyez tout simplement pas comme une épingle sur l’échelle). C'est juste une partie de la réalité et notre meilleur pari est de comprendre ses effets secondaires, pas d'attaquer les codeurs.
Independent

8

Dans l'un des commentaires, Mason a mentionné qu'un jour, nous parlerions d'un héritage considéré comme nuisible .

J'espere.

Le problème de l'héritage est à la fois simple et mortel, il ne respecte pas l'idée qu'un concept doit avoir une fonctionnalité.

Dans la plupart des langages OO, lorsque vous héritez d'une classe de base, vous:

  • hériter de son interface
  • hériter de son implémentation (données et méthodes)

Et voici devenir le problème.

Ce n’est pas la seule approche, bien que 00 langues s’y retrouvent pour la plupart. Heureusement, il existe des interfaces / classes abstraites dans celles-ci.

En outre, le manque de facilité pour faire autrement contribue à en faire une utilisation largement utilisée: franchement, même en sachant cela, hériteriez-vous d'une interface et intégreriez-vous la classe de base par composition, en déléguant la plupart des appels de méthodes? Ce serait bien mieux, vous seriez même averti si une nouvelle méthode apparaissait soudainement dans l'interface et que vous deviez choisir consciemment comment l'implémenter.

En contre-point, Haskelln'autorisez le principe de Liskov que pour "dériver" d'interfaces pures (appelées concepts) (1). Vous ne pouvez pas dériver d'une classe existante, seule la composition vous permet d'incorporer ses données.

(1) Les concepts peuvent fournir un défaut raisonnable pour une implémentation, mais comme ils ne disposent pas de données, ce défaut ne peut être défini que par rapport aux autres méthodes proposées par le concept ou en terme de constantes.


7

La réponse simple est: l’héritage a un couplage supérieur à la composition. Étant donné deux options, de qualités par ailleurs équivalentes, choisissez celle qui est moins couplée.


2
Mais c'est l'essentiel. Ils ne sont pas "autrement équivalents". L'héritage permet la substitution de Liskov et le polymorphisme, qui sont le point essentiel de l'utilisation de la POO. La composition ne
Mason Wheeler

4
Le polymorphisme / LSP ne constitue pas "le but principal" de la POO, mais une caractéristique. Il y a aussi encapsulation, abstractions, etc. L'héritage implique une relation "est une", les modèles d'agrégation une relation "a".
Steve

@Steve: Vous pouvez faire des abstractions et des encapsulations dans n'importe quel paradigme prenant en charge la création de structures de données et de procédures / fonctions. Mais le polymorphisme (faisant spécifiquement référence à la distribution de méthode virtuelle dans ce contexte) et la substitution de Liskov sont uniques à la programmation orientée objet. C'est ce que je voulais dire par là.
Mason Wheeler

3
@Mason: L'objectif est de "favoriser la composition par rapport à l'héritage". Cela ne signifie en aucun cas que vous n'utilisez ni même évitez l'héritage. Tout cela dit que lorsque toutes les autres choses sont égales, choisissez la composition. Mais si vous avez besoin d'héritage, utilisez-le!
Jeffrey Faust

7

Je pense que ce genre de conseil revient à dire "préférez conduire à voler". Autrement dit, les avions présentent toutes sortes d'avantages par rapport aux voitures, mais cela vient avec une certaine complexité. Donc, si beaucoup de gens essaient de voler du centre-ville vers la banlieue, le conseil dont ils ont vraiment besoin d'entendre, c'est qu'ils n'ont pas besoin de voler. En fait, voler rendra les choses plus compliquées à long terme, même si cela semble cool / efficace / facile à court terme. Alors que lorsque vous avez besoin de voler, il est généralement supposé être évident.

De même, l'héritage peut faire des choses que la composition ne peut pas, mais vous devriez l'utiliser quand vous en avez besoin, pas quand vous n'en avez pas besoin. Donc, si vous n'êtes jamais tenté d'assumer simplement que vous avez besoin d'héritage, vous n'avez pas besoin des conseils de "préférez la composition". Mais beaucoup de gens le font , et font besoin que des conseils.

C'est supposé implicite que lorsque vous AVEZ vraiment besoin d'un héritage, c'est évident, et vous devriez l'utiliser à ce moment-là.

Aussi, la réponse de Steven Lowe. Vraiment, vraiment ça.


1
J'aime votre analogie
barrylloyd

2

L'héritage n'est pas fondamentalement mauvais et la composition n'est pas intrinsèquement bonne. Ce ne sont que des outils qu'un programmeur orienté objet peut utiliser pour concevoir des logiciels.

Quand vous regardez une classe, est-ce qu'elle fait plus que ce qu'il devrait absolument faire ( SRP )? Est-ce une fonctionnalité de duplication inutile ( DRY ), ou est-il trop intéressé par les propriétés ou les méthodes d'autres classes ( Feature Envy ) ?. Si la classe viole tous ces concepts (et peut-être davantage), tente-t-elle d'être une classe de Dieu ? Il existe un certain nombre de problèmes qui peuvent survenir lors de la conception d'un logiciel, aucun d'entre eux n'étant nécessairement un problème d'héritage, mais pouvant rapidement créer des maux de tête importants et des dépendances fragiles dans lesquelles le polymorphisme a également été appliqué.

La source du problème est probablement moins un manque de compréhension sur l'héritage, et plus d'un choix médiocre en termes de conception, ou peut-être de ne pas reconnaître les "odeurs de code" relatives à des classes qui ne respectent pas le principe de responsabilité unique . Le polymorphisme et la substitution de Liskov ne doivent pas être écartés au profit de la composition. Le polymorphisme lui-même peut être appliqué sans recourir à l'héritage, ce sont des concepts tout à fait complémentaires. Si appliqué pensivement. L'astuce consiste à garder votre code simple et propre et à ne pas craindre d'être trop préoccupé par le nombre de classes que vous devez créer pour créer une conception système robuste.

En ce qui concerne la question de privilégier la composition au détriment de l'héritage, il ne s'agit en réalité que d'un autre cas d'application réfléchie des éléments de conception qui a le plus de sens en termes de résolution du problème. Si vous n'avez pas besoin d'hériter du comportement, alors vous ne devriez probablement pas, car la composition aidera à éviter les incompatibilités et les efforts de refactorisation majeurs qui s'imposent par la suite. Si par contre vous constatez que vous répétez beaucoup de code de telle sorte que toute la duplication soit centrée sur un groupe de classes similaires, il se peut alors que la création d'un ancêtre commun aiderait à réduire le nombre d'appels et de classes identiques. vous devrez peut-être répéter entre chaque classe. Ainsi, vous favorisez la composition, mais vous ne supposez pas que l'héritage ne soit jamais applicable.


-1

Je pense que la citation proprement dite vient de "Effective Java" de Joshua Bloch, où elle porte le titre de l'un des chapitres. Il affirme que l'héritage est sécurisé dans un package et chaque fois qu'une classe a été spécifiquement conçue et documentée pour être étendue. Il affirme, comme d'autres l'ont noté, que l'héritage rompt l'encapsulation, car une sous-classe dépend des détails de mise en œuvre de sa super-classe. Cela conduit, dit-il, à la fragilité.

Bien qu'il n'en parle pas, il me semble que l'héritage unique en Java signifie que la composition vous donne beaucoup plus de flexibilité que l'héritage.

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.