web-dev-qa-db-fra.com

Pourquoi l'héritage est-il généralement considéré comme une mauvaise chose par les partisans de OOP

Je continue d'entendre la phrase "Favoriser la composition plutôt que l'héritage" du GoF, qui est mentionnée à plusieurs reprises de manière ennuyeuse par mon ami qui pense que c'est une déclaration générale valide, mais n'est-il pas plus raisonnable de considérer que la vraie raison pour laquelle l'héritage existe n'est pas seulement pour le comportement réutiliser mais dans le cas où une hiérarchie de classes héritant du même ancêtre doit être passée comme arguments à une fonction, pour implémenter implicitement le polymorphisme et ces classes descendantes ont des méthodes qui sont des polymorphismes d'une fonction dans la classe ancestrale. L'utilisation de l'héritage de cette manière est la seule raison pour laquelle les troupeaux et les flottes de programmeurs trouvent agréable d'écrire l'interface utilisateur avec WPF de Microsoft avec (Visual) C #. Comment la composition s'intégrerait-elle bien dans l'image de la conception du cadre de l'interface utilisateur sans être trop verbeuse?

Je sais que les conflits de compatibilité peuvent résulter de l'utilisation de l'héritage, qui est l'une des choses que la déclaration vise à protéger, mais n'est-ce pas une question de mauvaise conception de la part du programmeur (échec à discerner quand hériter et quand composer ) plutôt que sur le paradigme ou la langue?

PS: J'ai utilisé les termes ancêtre et descendant pour éviter d'impliquer la profondeur de l'héritage par l'utilisation du parent et de l'enfant.

22
RonaldMunodawafa

Vous demandez pourquoi, comme si le si était décidé. Considérons votre supposition:

Pourquoi l'héritage est-il généralement considéré comme une mauvaise chose par OOP partisans

Je ne suis pas d'accord avec la prémisse de la question. L'héritage n'est généralement pas considéré comme mauvais, il est considéré comme mal utilisé et surutilisé.

GoF Design Patterns ne dit rien de mal à ce sujet.

Voyons ce que GoF Design Patterns dit réellement ...

  1. p20 - Dans la discussion de Favor composition over inheritance, que L'héritage et la composition des objets fonctionnent donc ensemble.
  2. p22 - La composition d'objet vous permet de modifier le comportement en cours de composition, mais elle nécessite également une indirection et peut être moins efficace.
  3. p25 - ... d'autre part, une utilisation intensive de la composition d'objets peut rendre les conceptions plus difficiles à comprendre.

Etc. Maintenant, il y a aussi beaucoup d'inconvénients à l'héritage que je pourrais citer, mais le fait est que le GoF ne prétend pas que l'héritage est mauvais et enseigne clairement les avantages et les inconvénients de diverses techniques. Que signifie favour dans ce contexte? Ne faites pas de l'héritage votre premier choix. Un peu comme ne faites pas du pied de biche votre premier choix, essayez d'abord la clé.

En ce qui concerne l'héritage, il existe deux types principaux, l'héritage de classe et l'héritage d'interface (sous-typage). Les deux sont appropriés pour des modèles particuliers, mais ce dernier est en faveur depuis la publication du GoF. Si vous appreniez OOP au début des années 90, vous connaîtriez la méthode Booch et la méthode Rumbaugh, mais de nos jours vous en entendez peu parler. Pourtant, ils enseignaient une génération de C++ (et Java) programmeurs à hériter et remplacer. Mais l'état de l'art a évolué. Nous avons, grâce à une expérience collective, constaté que le couplage lâche est vraiment trouvé grâce à l'utilisation d'interfaces et d'injection de dépendances, et pas simplement en utilisant l'encapsulation.

Le problème avec l'héritage est que trop de programmeurs "OO" considèrent les problèmes d'abord comme des solutions d'héritage potentielles. L'hérédité est un concept concret que les gens comprennent rapidement dès le début, tant de gens s'y accrochent et ne grandissent jamais vraiment en tant que modélisateurs de systèmes. Lorsque tout dans un système est coincé dans une hiérarchie d'héritage et que le code est réutilisé par l'héritage de classe, cela donne des résultats désastreux et les avantages de OO sont contrebalancés par la lutte avec la hiérarchie elle-même. J'ai vu de nombreux projets, dont j'ai été moi-même responsable, qui ont passé plus de temps à lutter contre une énorme hiérarchie qu'à maintenir les interfaces de sous-système, ce qui est un signe d'abus d'héritage.

Mais cela mis à part, l'héritage est inestimable pour accomplir de nombreuses bonnes choses. Le polymorphisme (en particulier le polymorphisme de sous-type) est un outil très puissant et la répartition simple et double est la clé pour des modèles tels que le modèle de visiteur. L'héritage des interfaces est essentiel pour concevoir des interfaces, pas des implémentations. Les classes ou interfaces abstraites ne sont utiles qu'avec l'héritage.

Si votre ami pense que "privilégier la composition plutôt que l'héritage" est un mantra pour éviter complètement l'héritage, il se trompe et ne comprend pas le concept d'un ensemble d'outils complet.

On dirait qu'il n'a pas réellement lu GoF. S'il a prétendu le lire, alors il semble qu'il l'ait mal compris et devrait le relire.

34
codenheim

... mon ami qui pense que c'est une déclaration générale valable mais n'est-il pas plus sensé de ...

Oui, il est toujours plus judicieux (si le temps le permet) de comprendre pourquoi les choses sont telles qu'elles sont plutôt que de suivre un mantra aveuglément.

la vraie raison pour laquelle l'héritage existe n'est pas seulement pour la réutilisation des comportements mais pour le cas où une hiérarchie de classes héritant du même ancêtre doit être passée comme arguments à une fonction

Pas correcte. C'est un détail d'implémentation des langues, et bien qu'il soit assez important - pas ce sur quoi vous devriez vous concentrer.

pour implémenter implicitement le polymorphisme et ces classes descendantes ont des méthodes qui sont des polymorphismes d'une fonction dans la classe ancestrale.

Et cela aussi n'est pas tout à fait correct.

n'est-ce pas une mauvaise conception de la part du programmeur (incapacité à discerner quand hériter et quand composer) plutôt que le paradigme ou le langage?

Il s'agit entièrement d'une mauvaise conception de la part du programmeur. Le dédain général pour l'héritage n'est pas pour l'héritage du concept, c'est pour l'héritage utilisé dans le code qui cause de la douleur.

Parce que l'héritage (et tous les sous-types) est une spécialisation d'un super-type. Non seulement cela, mais l'héritage bien fait est une spécialisation des contrats définis par le super type (sinon l'ensemble Rectangle/Square chose fonctionnerait réellement). Même lorsque les programmeurs savent qu'ils sont censés le faire (et très peu le font). Il est difficile d'affiner les contrats du type sans casser d'autres parties des contrats (voir les comportements Width/Height dans Rectangle/Square pour un exemple ). Et pire encore, il n'est pas particulièrement courant que vous souhaitiez que fasse réellement cette spécialisation limitée car elle est rarement utile.

Donc, à la place, il y a une cargaison de programmeurs qui utilisent mal l'héritage, juste pour faire du polymorphisme, ou pour avoir XY et Z (virer sur un champ supplémentaire est une utilisation fragile et souvent dangereuse de l'héritage), ou pour un peu spécialiser les choses (oui, Penguin est un Bird, mais sa méthode .Fly() ne fonctionne pas ...), ou ...

L'héritage est donc considéré comme une chose généralement mauvaise, car il donne généralement de mauvais résultats.

8
Telastyn