Préférez la composition au cours de l'héritage?
Pourquoi préférer la composition au cours de l'héritage? Ce compromis, il y a pour chaque approche? Quand devriez-vous choisir d'héritage sur la composition?
- Voir aussi la classe dont le design est mieux
- Il y a un bon article sur la question ici. Mon opinion personnelle est qu'il n'existe pas de "meilleur" ou "pire" principe de conception. Il est "approprié" et "l'insuffisance" de la conception de la tâche concrète. En d'autres termes - je utiliser à la fois l'héritage ou de la composition, en fonction de la situation. L'objectif est de produire de plus petites code plus facile à lire, de réutilisation et de s'étendre plus loin.
- en une phrase l'héritage est public, si vous avez une méthode publique et de la changer ça change de la publication de l'api. si vous avez de la composition et de l'objet composé a changé, vous n'avez pas à changer votre api.
Vous devez vous connecter pour publier un commentaire.
Préfèrent la composition au cours de l'héritage comme il est plus malléable, facile à modifier par la suite, mais n'utilisez pas de composer toujours. Avec la composition, il est facile pour un changement de comportement à la volée avec l'Injection de Dépendances /Setters. L'héritage est plus rigide que la plupart des langues ne permet pas de tirer de plus d'un type. Si la chair est plus ou moins cuit une fois que vous dérivez de TypeA.
Mon test à l'acide pour le ci-dessus est:
par exemple, Un Cessna biplan, va exposer à l'interface complète d'un avion, si pas plus. De sorte que le rend apte à tirer de l'Avion.
par exemple, Un Oiseau peut-être besoin que la mouche comportement d'un Avion. Dans ce cas, il est logique d'en extraire une interface /classe /les deux et en faire un membre de ces deux classes.
Mise à jour: Viens de me remettre à ma réponse et il semble maintenant qu'il est incomplet sans la mention expresse de Barbara Liskov de Principe De Substitution De Liskov comme un test pour " Devrais-je être héritent de ce type?'
Square
deRectangle
(en.wikipedia.org/wiki/Circle-ellipse_problem). Je ne suis pas convaincu que, sans plus de précisions "est un" est un test significatif.RubberDuck
classe encore estDuck
, et comme telle, elle souffre encore d'une structure rigide. LeFlyNoWay
de la classe videfly()
méthode est la même que pour un videfly()
à une mauvaise utilisation de succession dans la première place. Cela peut également constituer une violation de la LSP, depuis les clients de tous lesDuck
objets vont s'attendre àperformFly()
. Un canard en caoutchouc qui neperformFly()
de ne pas avoir été déplacés, par exemple, et le client peut s'attendre à un canard de ne pas être dans le même endroit après le vol.Pense de confinement comme un a un relation. Une voiture "a un moteur, une personne "a un" nom", etc.
Penser à l'héritage comme un est un relation. Une voiture "qui est" un véhicule, une personne "est un" mammifère, etc.
Je ne vais pas prendre le crédit pour cette approche. Je l'ai pris directement à partir de la Deuxième Édition de Code Complet par Steve McConnell, la Section 6.3.
Si vous comprenez la différence, c'est plus facile à expliquer.
Code De Procédure
Un exemple de ceci est PHP sans l'utilisation de classes (en particulier avant de PHP5). Toute logique est encodé dans un jeu de fonctions. Vous pouvez inclure d'autres fichiers contenant les fonctions d'assistance et ainsi de suite et la conduite de vos affaires de la logique par le passage des données autour de dans les fonctions. Ceci peut être très difficile à gérer, car la demande augmente. PHP5 essaie de remédier à cela en offrant plus de la conception orientée objet.
Héritage
Cela encourage l'utilisation des classes. L'héritage est l'un des trois principes de OO design (héritage, polymorphisme, encapsulation).
C'est l'héritage au travail. L'Employé "est" une Personne ou hérite de Personne. Toutes les relations d'héritage sont de type "est-un" relations". Employé également ombres le Titre de propriété de la Personne, sens de l'Employé.Le titre sera de retour le Titre de l'Employé n'est pas la Personne.
Composition
Composition est privilégié par rapport à l'héritage. Pour faire très simple, vous auriez:
Composition est généralement "a un" ou "un" de la relation. Ici, l'Employé de classe a d'une Personne. Il n'hérite pas d'une Personne mais il récupère à la Personne objet passé en elle, c'est pourquoi il "a une" Personne".
La Composition au cours de l'Héritage
Maintenant, imaginons que vous vouliez créer un type de Gestionnaire si vous vous retrouvez avec:
Cet exemple fonctionne correctement, cependant, que si la Personne et Employé à la fois déclaré
Title
? Devrait Manager.Le titre de retour de "directeur des Opérations" ou "Monsieur"? En vertu de la composition de cette ambiguïté est mieux géré:Le Gestionnaire d'objet est composé comme un Employé et une Personne. Le Titre comportement est pris de l'employé. Cette explicites composition supprime l'ambiguïté entre autres choses, et vous rencontrerez moins de bugs.
Avec tous les indéniables avantages fournis par l'héritage, voici quelques-uns de ses inconvénients.
Les inconvénients de l'Héritage:
Sur l'autre main composition d'Objet est défini au moment de l'exécution à travers des objets d'acquérir des références à d'autres objets. Dans un tel cas, ces objets ne seront jamais en mesure d'atteindre les uns les autres protégés de données (pas de briser l'encapsulation) et sera forcé de se respecter les uns les autres de l'interface. Et dans ce cas également, la mise en œuvre des dépendances-être beaucoup moins que dans le cas de l'héritage.
Un autre, très pragmatique de la raison, de préférer la composition au cours de l'héritage a à voir avec votre modèle de domaine, et de la cartographie à une base de données relationnelle. C'est vraiment dur à la carte de l'héritage du modèle SQL (vous vous retrouvez avec toutes sortes de hacky solutions de contournement, comme la création de colonnes qui ne sont pas toujours utilisé, à l'aide de points de vue, etc). Certains ORMLs d'essayer de traiter avec cela, mais il obtient toujours compliqué rapidement. La Composition peut être facilement modélisé par une relation de clé étrangère entre les deux tables, mais l'héritage est beaucoup plus difficile.
Tout en quelques mots, je suis d'accord avec "Préfèrent la composition au cours de l'héritage", très souvent, pour moi, ça sonne comme "préfèrent les pommes de terre dans du coca-cola". Il existe des lieux de l'héritage et des lieux pour la composition. Vous devez comprendre la différence, alors cette question va disparaître. Ce que cela signifie vraiment pour moi, c'est "si vous allez utiliser l'héritage - détrompez-vous, les chances sont que vous avez besoin de la composition".
Vous devriez préférer les pommes de terre dans de coca-cola, lorsque vous voulez manger, et de coca-cola sur les pommes de terre quand vous voulez boire.
La création d'une sous-classe doit signifier plus que juste un moyen pratique pour appeler les méthodes de la superclasse. Vous devriez utiliser l'héritage lors de la sous-classe "est-un" super classe à la fois structurellement et fonctionnellement, quand il peut être utilisé en tant que super-classe, et vous allez l'utiliser. Si ce n'est pas le cas - il n'est pas de l'héritage, mais quelque chose d'autre. La Composition est quand vos objets se compose de l'autre, ou a une certaine relation à eux.
Donc, pour moi, c'est comme si quelqu'un ne sait pas si il a besoin de l'héritage ou de la composition, le vrai problème, c'est qu'il ne sait pas si il a envie de boire ou de manger. Pensez à votre problème de domaine de plus, de mieux le comprendre.
InternalCombustionEngine
avec une classe dérivéeGasolineEngine
. Ce dernier ajoute des choses comme les bougies d'allumage, qui est la classe de base manque, mais l'utilisation de la chose comme unInternalCombustionEngine
sera la cause de la bougie d'allumage pour s'habituer.L'héritage est assez alléchant, surtout venant de la procédure-la terre et, souvent, elle ressemble étonnamment élégant. Je veux dire tout ce que je dois faire est d'ajouter ce un peu de fonctionnalité à une autre classe, non? Eh bien, l'un des problèmes est que
l'héritage est probablement la pire forme de couplage, vous pouvez avoir
Votre classe de base des sauts de l'encapsulation en exposant les détails de mise en œuvre de sous-classes dans le formulaire de membres protégés. Cela rend votre système rigide et fragile. Le plus tragique faille, cependant, est la nouvelle sous-classe apporte avec elle tout le bagage et l'opinion de la chaîne d'héritage.
L'article, L'héritage est le Mal: L'Epic Fail de la DataAnnotationsModelBinder, promenades à travers un exemple en C#. Il montre l'utilisation de la transmission lors de la composition ont été utilisés et comment il pourrait être remaniée.
En Java ou en C#, un objet ne peut pas changer son type une fois qu'il a été instancié.
Donc, si votre objet doit apparaître comme un objet différent ou de se comporter différemment selon l'état de l'objet ou les conditions, puis utilisez Composition: reportez-vous à État et Stratégie les Modèles de Conception.
Si l'objet doivent être du même type, puis utilisez Héritage ou de mettre en œuvre des interfaces.
Client
. Ensuite, un nouveau concept dePreferredClient
apparaît plus tard. DevraitPreferredClient
hériterClient
? Un client privilégié "est un" client après tout, non? Eh bien, pas si vite... comme vous l'avez dit les objets ne peuvent pas changer de classe au moment de l'exécution. Comment voulez-vous le modèle de laclient.makePreferred()
opération? Peut-être que la réponse se trouve dans l'utilisation de la composition avec un manque de concept, d'uneAccount
peut-être?Client
classes, peut-être il y a juste un qui encapsule le concept d'unAccount
qui pourrait être unStandardAccount
ou unPreferredAccount
...Personnellement, j'ai appris à toujours préférer la composition au cours de l'héritage. Il n'est pas programmatique problème que vous pouvez résoudre avec l'héritage qui vous ne pouvez pas résoudre avec la composition; bien que vous pourriez avoir à utiliser des Interfaces(Java) ou de Protocoles(Obj-C) dans certains cas. Depuis C++ ne sait pas quelque chose, vous aurez à utiliser les classes de base abstraites, ce qui signifie que vous ne pouvez pas obtenir entièrement débarrasser de l'héritage en C++.
Composition est souvent plus logique, elle offre une meilleure abstraction, une meilleure encapsulation, une meilleure réutilisation de code (en particulier dans les très grands projets) et est moins susceptible de casser quelque chose, à une distance juste parce que vous avez isolé le changement de n'importe où dans votre code. Il rend également plus facile à défendre la "Principe de Responsabilité Unique", qui est souvent désigné comme "Il ne devrait jamais y avoir plus d'une raison pour une classe de changement.", et cela signifie que chaque classe a un rôle spécifique et qu'elle ne devrait avoir des méthodes qui sont directement liées à son objet. Aussi avoir très peu profonde, arbre d'héritage, il est beaucoup plus facile de garder la vue d'ensemble, même lorsque votre projet commence à devenir vraiment grand. Beaucoup de gens pensent que l'héritage représente notre monde réel assez bien, mais ce n'est pas la vérité. Le monde réel utilise beaucoup plus la composition de l'héritage. À peu près chaque objet du monde réel, vous pouvez tenir dans votre main a été composées à l'autre, plus petit du monde réel des objets.
Il y a des inconvénients de composition, même si. Si vous ignorez la succession purement et simplement et de se concentrer uniquement sur la composition, vous remarquerez que vous avez souvent à écrire quelques lignes de code qui n'étaient pas nécessaires si vous aviez utilisé l'héritage. Vous êtes également parfois obligés de se répéter et cela viole les Principe SEC (SEC = Don't Repeat Yourself). Aussi la composition nécessite souvent délégation, et une méthode est juste d'appeler une autre méthode d'un autre objet avec aucun autre code entourant cet appel. Ces "doubles appels de méthode" (ce qui peut facilement s'étendre à triple ou quadruple appels de méthode et de plus loin) ont beaucoup de moins bonnes performances que l'héritage, où vous avez simplement à hériter d'une méthode de votre parent. L'appel d'une méthode héritée peut être aussi rapide que d'appeler un non-hérité de l'une, ou il peut être un peu plus lent, mais elle est en général plus rapide que deux fois de suite les appels de méthode.
Vous avez peut-être remarqué que la plupart des langages à objets ne permet pas l'héritage multiple. Bien qu'il existe quelques cas où l'héritage multiple peut vraiment vous acheter quelque chose, mais ce sont plutôt des exceptions que la règle. Chaque fois que vous exécutez dans une situation où vous pensez que "l'héritage multiple serait vraiment un truc cool pour résoudre ce problème", vous êtes habituellement à un point où vous devez re-penser l'héritage tout à fait, puisque même il faudrait peut-être quelques lignes de code, une solution basée sur la composition sera généralement beaucoup plus élégant, souple et évolutive..
L'héritage est vraiment une fonctionnalité intéressante, mais je crains que ça a été galvaudé depuis quelques années. Les personnes traitées à l'héritage comme un marteau qui peut clou en tout, peu importe si c'était réellement un clou, une vis, ou peut-être quelque chose de complètement différent.
N'ai pas trouvé une réponse satisfaisante ici, j'ai donc écrit une nouvelle.
De comprendre pourquoi "préfèrent la composition au cours de l'héritage", il nous faut d'abord revenir l'hypothèse omis dans ce raccourci de langage.
Il y a deux avantages de l'héritage: le sous-typage et sous-classement
De sous-typage moyens conformes à un type (interface) la signature, c'est à dire un ensemble d'Api, et l'on peut remplacer une partie de la signature d'atteindre le sous-typage polymorphisme.
Sous-classement moyen implicite de la réutilisation de la méthode mise en œuvre.
Avec les deux avantages viennent avec deux fins différentes pour faire de l'héritage: sous-typage et orientée vers la réutilisation de code orienté.
Si la réutilisation de code est la seule but, sous-classement peut donner plus que ce dont il a besoin, c'est à dire quelques méthodes publiques de la classe parent n'a pas beaucoup de sens pour l'enfant de la classe. Dans ce cas, au lieu de favoriser la composition au cours de l'héritage, la composition est exigé. C'est aussi là le "est-un" vs "a-un" notion vient de.
De sorte que lorsque le sous-typage est résolu, c'est à dire l'utilisation de la nouvelle classe plus tard dans un polymorphe manière, ne nous sommes confrontés au problème du choix de l'héritage ou de la composition. C'est l'hypothèse qui obtient omis dans la version raccourcie de l'idiome en discussion.
De sous-type est conforme à un type de signature, cela signifie que la composition a toujours pour exposer pas moins le montant de l'Api de type. Maintenant, le commerce offs de coup de pied dans:
Héritage offre simple réutilisation de code si elle n'est pas remplacée, tandis que la composition a à re-coder toutes les API, même si c'est juste un simple travail de la délégation.
Héritage fournit simples ouvrir la récursivité via l'interne site polymorphe
this
, c'est à dire invocation de méthode de remplacement (ou même type) dans un autre etat membre de la fonction, publique ou privée (bien que découragé). Ouvrir la récursivité peut être simulé par la composition, mais il exige un effort supplémentaire et ne peut pas toujours viable(?). Cette réponse à un double question parle de quelque chose de similaire.Héritage expose protégé membres. Cela rompt l'encapsulation de la classe parent, et si elle est utilisée par la sous-classe, une autre dépendance entre l'enfant et son parent est introduit.
Composition a la conviennent de l'inversion de contrôle, et de sa dépendance peut être injecté de façon dynamique, comme c'est indiqué dans décorateur modèle et modèle de proxy.
Composition a l'avantage de combinator-orienté de programmation, c'est à dire travailler d'une manière comme de l' diagramme composite.
Composition suit immédiatement programmation d'une interface.
Composition a l'avantage de facile l'héritage multiple.
Ci-dessus, le commerce lié à l'esprit, nous nous sommes donc préfèrent la composition au cours de l'héritage. Pourtant, pour étroitement liées classes, c'est à dire quand implicite de la réutilisation de code vraiment apporter des avantages, ou le pouvoir magique d'ouvrir la récursivité est souhaitée, l'héritage doit être le choix.
Mes règle générale: Avant l'utilisation de l'héritage, examiner si la composition a plus de sens.
Raison: sous-classement signifie généralement plus de complexité et d'interdépendance, c'est à dire plus difficiles à modifier, maintenir et mettre à l'échelle sans faire de fautes.
Beaucoup plus complète et concrète réponse de Tim Boudreau de Soleil:
L'héritage est très puissant, mais vous ne pouvez pas le forcer (voir: la cercle ellipse problème). Si vous ne pouvez vraiment pas être complètement sûr d'un vrai "est-un" sous-type de relation, alors il est préférable d'aller avec la composition.
Pourquoi préférer la composition au cours de l'héritage?
Voir d'autres réponses.
Quand devriez-vous choisir d'héritage sur la composition?
Chaque fois que la phrase "un Bar" est un truc, et un Bar peut faire tout ce qu'un Foo peut le faire" de sens.
La sagesse conventionnelle dit que si la phrase "un Bar" est un Foo" a du sens, alors c'est une bonne indication que le choix de l'héritage est approprié. Par exemple, un chien est un animal, donc d'avoir le Chien classe hérite de l'Animal est probablement une bonne conception.
Malheureusement, ce simple "est-un" test n'est pas fiable. Le Cercle Ellipse problème est un excellent contre-exemple: même si un cercle est une ellipse, c'est une mauvaise idée d'avoir le Cercle classe hérite de l'Ellipse, car il y a des choses que ellipses peut faire, mais les cercles ne le peuvent pas. Par exemple, les points de suspension peuvent s'étirer, mais les cercles ne le peuvent pas. Ainsi, alors que vous pouvez avoir
ellipse.stretch()
, vous ne pouvez pas avoircircle.stretch()
.C'est pourquoi un meilleur test est "un Bar" est un Foo, et un Bar peut faire tout ce qu'un Foo peut faire". Cela signifie vraiment que Foo peut être utilisé polymorphically. Le "est-un" test n'est qu'une condition nécessaire pour polymorphe à utiliser, et signifie généralement que tous les getters de Foo a de sens que dans un Bar. Les autres "peut-faire-tout" test signifie que tous les poseurs de Foo également avoir du sens dans un Bar. Ce test supplémentaire échoue généralement quand une classe Bar "est-un" Foo, mais ajoute quelques contraintes, dans ce cas, vous ne devez pas utiliser l'héritage, parce que Toto n'a pas pu être utilisé polymorphically. En d'autres mots, l'héritage n'est pas sur les propriétés de partage, mais à propos de la fonctionnalité de partage de. Les classes dérivées doivent étendre la fonctionnalité des classes de base, et non de la restreindre.
C'est l'équivalent de la Principe De Substitution De Liskov:
computeArea(Circle* c) { return pi * square(c->radius()); }
. Il est cassé évidemment si elle est adoptée une Ellipse (ce qui n'rayon() même dire?). Une Ellipse est pas un Cercle, et en tant que tel ne devrait pas tirer de Cercle.computeArea(Circle *c) { return pi * width * height / 4.0; }
Maintenant il est générique.width()
etheight()
? Que si maintenant, un utilisateur de la bibliothèque décide de créer une autre classe appelée "Oeuf"? Faut-il également dériver de "Cercle"? Bien sûr que non. Forme d'oeuf n'est pas un cercle et une ellipse n'est pas un cercle, donc personne ne doit dériver de Cercle, puisqu'elle brise la LSP. Les méthodes d'exécution de l'opération sur un Cercle* classe de faire des hypothèses fortes sur ce qu'est un cercle, et la rupture de ces hypothèses sera presque certainement conduire à des bugs.Héritage crée une relation forte entre une sous-classe de super-classe, sous-classe doit être conscient de la super-classe es détails de mise en œuvre. La création de la super-classe est beaucoup plus difficile, quand vous avez à penser à la façon dont elle peut être prolongée. Vous avez de la classe de document invariants soigneusement, et de l'état de quelles autres méthodes méthodes substituables utiliser en interne.
Héritage est parfois utile, si la hiérarchie représente vraiment un est-une-relation. Il se rapporte à Ouvert-Fermé Principe, qui stipule que les classes doivent être fermées pour la modification, mais ouvert à d'extension. De cette façon, vous pouvez avoir polymorphisme; avoir une méthode générique qui traite avec un super type et de ses méthodes, mais par l'intermédiaire de répartition dynamique de la méthode de la sous-classe est appelée. C'est flexible, et permet de créer des indirection, ce qui est essentiel dans le logiciel (à savoir moins sur les détails de l'implémentation).
L'héritage est facilement galvaudé, cependant, et crée une complexité supplémentaire, avec beaucoup de dépendances entre les classes. Aussi la compréhension de ce qui se passe lors de l'exécution d'un programme est assez difficile en raison de couches et de sélection dynamique des appels de méthode.
Je voudrais suggérer à l'aide de la composition en tant que par défaut. Il est plus modulaire, et offre l'avantage de la liaison tardive (vous pouvez changer le composant dynamiquement). Aussi, il est plus facile de tester les choses séparément. Et si vous avez besoin d'utiliser une méthode d'une classe, vous n'êtes pas forcé d'être d'une certaine forme (Principe de Substitution de Liskov).
Inheritance is sometimes useful... That way you can have polymorphism
que de lier entre eux les concepts d'héritage et de polymorphisme (sous-typage supposé, vu le contexte). Mon commentaire était destiné à préciser ce qui vous préciser dans votre commentaire: que l'héritage n'est pas la seule façon de mettre en œuvre le polymorphisme, et, en fait, n'est pas nécessairement le facteur déterminant au moment de choisir entre la composition et l'héritage.Supposons qu'un avion n'a que deux pièces: un moteur et les ailes.
Ensuite, il y a deux façons de concevoir un avion de la classe.
Maintenant votre avion peut commencer à avoir des ailes
et de le modifier, de le rotary ailes de la mouche. C'est essentiellement
un moteur avec des ailes. Mais que si je voulais changer
le moteur à la volée ainsi?
Soit la classe de base
Engine
expose un mutateur pour changer sonpropriétés, ou je la refonte
Aircraft
comme:Maintenant, je peux remplacer mon moteur à la volée ainsi.
Vous avez besoin d'avoir un coup d'oeil à Le Principe De Substitution De Liskov de l'Oncle Bob est SOLIDE principes de conception de classe. 🙂
"Préfèrent la composition au cours de l'héritage" est un principe de conception, qui dit de ne pas abuser de l'héritage, quand il ne rentre pas.
Si pas de monde réel lien de subordination existe entre les deux entités, ne pas utiliser l'héritage, mais au lieu d'utiliser la composition. La Composition représente "A UNE" relation".
E. g. UNE voiture à Roues, carrosserie, moteur, etc. Mais si vous l'héritage ici, il devient de VOITURE EST UNE Roue qui est incorrect.
Pour de plus amples explications, reportez-vous à préférez la composition au cours de l'héritage
Quand vous voulez "copier"/Exposer la classe de base de l'API, vous utiliser l'héritage. Quand vous voulez seulement de la "copie" de la fonctionnalité, l'utilisation de la délégation.
Un exemple: Vous voulez créer une Pile à partir d'une Liste. Pile a seulement pop, push et peek. Vous ne devriez pas utiliser l'héritage étant donné que vous ne voulez pas push_back, push_front, removeAt, et al.-type de fonctionnalité dans une Pile.
De côté de est a/a a considérations, on doit également considérer la "profondeur" de l'héritage de votre objet doit passer par. Rien au-delà de cinq ou six niveaux de l'héritage profonde peut provoquer inattendu casting et boxing/unboxing de problèmes, et dans ces cas, il pourrait être sage de composer votre objet à la place.
Ces deux façons de vivre ensemble est très bien et effectivement soutenir les uns les autres.
Composition est juste à jouer c'est modulaire: vous créez une interface similaire à la classe parent, de créer de nouveaux objets et délégué appelle à elle. Si ces objets n'ont pas besoin de savoir les uns des autres, il est tout à fait sûr et facile à utiliser la composition. Il y a donc beaucoup de possibilités ici.
Toutefois, si le parent de la classe pour une raison quelconque besoin d'accéder à des fonctions fournies par les "enfants de la classe" pour les inexpérimentés programmeur, il peut ressembler c'est un endroit idéal pour utiliser l'héritage. La classe parent peut simplement appeler son propre résumé "foo()" qui est remplacée par la sous-classe et puis, il peut donner de la valeur à la base abstraite.
Il ressemble à une bonne idée, mais dans de nombreux cas, il est préférable de simplement donner la classe d'un objet qui implémente l'foo() (ou même de définir la valeur indiquée foo() manuellement) que pour hériter de la nouvelle classe à partir de certains de la classe de base qui nécessite de la fonction foo() doit être spécifié.
Pourquoi?
Parce que l'héritage est une mauvaise façon de déplacement de données.
La composition a un réel avantage ici: la relation peut être inversée: la classe mère" ou "abstraite" travailleur peut agréger tout spécifique "des enfants" des objets de mise en œuvre de certaines interface + tout enfant peut être placé à l'intérieur de n'importe quel autre type de parent, qui l'accepte est de type. Et il peut y avoir un nombre quelconque d'objets, par exemple MergeSort ou QuickSort peut trier une liste d'objets de la mise en œuvre d'un résumé de Comparer l'interface. Ou pour le dire d'une autre façon: tout groupe d'objets qui mettent en œuvre des "foo()" et d'autres groupe d'objets, ce qui peut rendre l'utilisation des objets ayant "foo()" peuvent jouer ensemble.
Je peux penser à trois vraies raisons de l'utilisation de l'héritage:
Si elles sont vraies, alors il est probablement nécessaire pour utiliser l'héritage.
Il n'y a rien de mal à utiliser la raison 1, c'est une très bonne chose d'avoir une solide interface sur vos objets. Cela peut être fait à l'aide de la composition ou de l'héritage, pas de problème - si cette interface est simple et ne change pas. Habituellement, l'héritage est très efficace ici.
Si la raison est le numéro 2 ça devient un peu délicat. Avez-vous vraiment seulement besoin d'utiliser la même classe de base? En général, simplement en utilisant la même classe de base n'est pas assez bon, mais il peut être une exigence de votre cadre, une étude de conception qui ne peut pas être évitée.
Cependant, si vous souhaitez utiliser les variables privées, le cas 3, alors vous pourriez être en difficulté. Si vous envisagez de variables globales dangereux, alors vous devriez considérer l'utilisation de l'héritage pour obtenir l'accès aux variables privées aussi dangereuse. Rappelez-vous, les variables globales ne sont pas si mal QUE ça - les bases de données sont essentiellement de grand ensemble de variables globales. Mais si vous pouvez le manipuler, il est tout à fait bien.
Un moyen simple de donner un sens à cela serait que l'héritage doit être utilisé lorsque vous avez besoin d'un objet de votre classe à avoir le même interface comme son parent de la classe, de sorte qu'il peut ainsi être traité comme un objet de la classe parente (upcasting). En outre, les appels de fonction sur un objet de classe dérivée doit rester la même partout dans le code, mais la méthode à appeler serait déterminé au moment de l'exécution (c'est à dire le faible niveau mise en œuvre diffère, le haut niveau interface reste le même).
Composition doit être utilisé lorsque vous n'avez pas besoin d'une nouvelle classe d'avoir la même interface, c'est à dire que vous souhaitez cacher certains aspects de la classe dans la mise en œuvre desquels l'utilisateur de la classe n'a pas besoin de connaître. Si la composition est plus dans la façon de soutenir encapsulation (c'est à dire en occultant la mise en œuvre), tandis que l'héritage est destiné à soutenir abstraction (par exemple en fournissant une représentation simplifiée de quelque chose, dans ce cas, le même interface pour une gamme de types avec différents éléments internes).
Lorsque vous avez un est-un relation entre deux classes (exemple chien est un chien), vous allez pour l'héritage.
D'autre part, si vous avez a-un ou certains adjectif de relation entre deux classes (étudiant a des cours) ou (professeur d'études des cours), vous avez choisi la composition.
De sous-typage est approprié et plus puissant, où le les invariants peuvent être énumérés, sinon utiliser la fonction de composition pour l'extensibilité.
Je suis d'accord avec @Pavel, quand il dit, il y a des endroits pour la composition et il y a des endroits pour l'héritage.
Je pense que l'héritage doit être utilisé si votre réponse est affirmative à l'une de ces questions.
Toutefois, si votre intention est purement et simplement que de code de la réutilisation, puis la composition est probablement un meilleur choix de conception.
Une règle que j'ai entendu, c'est l'héritage doit être utilisé lorsque son "est-un" de la relation et de la composition lors de son une "a-un". Même avec que je sens que vous devriez toujours se pencher vers la composition car elle élimine beaucoup de complexité.
L'héritage est un très puissant mécanisme de réutilisation de code. Mais il doit être utilisé correctement. Je dirais que l'héritage est utilisé correctement si la sous-classe est aussi un sous-type de la classe parent. Comme mentionné ci-dessus, le Principe de Substitution de Liskov est le point clé ici.
Sous-classe n'est pas le même que le sous-type. Vous pouvez créer des sous-classes qui ne sont pas sous-types (et c'est quand vous devez utiliser la composition). Pour comprendre ce qu'est un sous-type est, permet de commencer à donner une explication de ce qu'un type est.
Quand nous disons que le nombre 5 est de type entier, nous sommes en indiquant que 5 appartient à un ensemble de valeurs possibles (à titre d'exemple, voir les valeurs possibles pour les types primitifs de Java). Nous sommes également en déclarant qu'il est valide d'un ensemble de méthodes, je peux effectuer sur la valeur comme l'addition et la soustraction. Et enfin nous indiquant qu'il y a un ensemble de propriétés qui sont toujours satisfaits, par exemple, si j'ajoute les valeurs 3 et 5, je vais avoir 8 en conséquence.
Pour donner un autre exemple, de réfléchir sur les types de données abstraite, Ensemble des entiers et Liste de nombres entiers, les valeurs qu'ils peuvent tenir sont limités à des entiers. Ils prennent en charge un ensemble de méthodes, telles que l'ajout(newValue) et la taille(). Et ils ont tous deux des propriétés différentes (classe invariant), des Ensembles de ne pas autoriser les doublons, tandis que la Liste n'autoriser les doublons (bien sûr il y a d'autres propriétés qu'ils ont à la fois satisfaire).
Sous-type est également un type, qui a une relation d'un autre type, appelé type parent (ou supertype). Le sous-type doit satisfaire aux caractéristiques (valeurs, des méthodes et des propriétés) du type parent. La relation signifie que, dans un contexte où le supertype est attendue, il peut être substituables par un sous-type, sans affecter le comportement de l'exécution. Allons voir un peu de code pour illustrer ce que je dis. Supposons que j'écris une Liste d'entiers (dans une sorte de pseudo langue):
Alors, j'écris l'Ensemble des entiers comme une sous-classe de la Liste d'entiers:
Nos Ensemble des entiers de la classe est une sous-classe de la Liste d'Entiers, mais n'est pas un sous-type, du fait qu'il n'est pas satisfaisant à toutes les fonctionnalités de la Liste de classe. Les valeurs, et la signature de l'méthodes sont satisfaits, mais les propriétés ne sont pas. Le comportement de l'ajouter(Entier) méthode a été clairement changé, ne pas conserver les propriétés du type parent. Pense que du point de vue du client, de vos classes. Ils reçoivent un Ensemble d'entiers où une Liste d'entiers est prévu. Le client peut vouloir ajouter une valeur et la valeur ajoutée à la Liste, même si cette valeur existe déjà dans la Liste. Mais à son habitude d'obtenir ce comportement si la valeur existe. Une grande surprise pour elle!
C'est un exemple classique d'une mauvaise utilisation de l'héritage. Utilisation de la composition dans ce cas.
(un fragment de: utiliser l'héritage correctement).
Pour répondre à cette question à partir d'un point de vue différent pour les nouveaux programmeurs:
L'héritage est souvent enseigné dès le début, quand nous apprendre la programmation orientée objet, il est donc considéré comme une solution simple à un problème commun.
Il a l'air génial, mais dans la pratique, presque jamais, jamais œuvres, pour une ou plusieurs raisons:
En fin de compte, nous attacher à notre code dans certains difficile de noeuds et d'obtenir aucun avantage que ce soit, sauf que nous obtenons à-dire "Cool, j'ai appris à propos de l'héritage et maintenant je l'ai utilisé." Ce n'est pas destiné à être condescendant parce que nous l'avons tous fait. Mais nous l'avons tous fait parce que personne ne nous a dit de ne pas.
Dès que quelqu'un a expliqué "favoriser la composition au cours de l'héritage" pour moi, je repense à chaque fois que j'ai essayé de partager les fonctionnalités entre les classes à l'aide de l'héritage et réalisé que la plupart du temps, il n'a pas vraiment bien.
L'antidote est la Principe De Responsabilité Unique. Pensez à cela comme une contrainte. Ma classe doit faire une chose. Je doit être en mesure de donner ma classe un nom qui décrit en quelque sorte qu'une chose qu'il ne. (Il y a des exceptions à tout, mais de règles absolues sont parfois plus lorsque nous sommes à l'apprentissage.) Il s'ensuit que je ne peux pas écrire une classe de base appelée
ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses
. Quelle que soit la fonctionnalité distincte j'ai besoin doit être dans sa propre classe, et puis d'autres classes qui ont besoin de cette fonctionnalité dépend que de la classe, pas héritent de celui-ci.Au risque de simplifier à outrance, c'est la composition la composition de plusieurs classes à travailler ensemble. Et une fois que nous en forme que d'habitude, nous constatons qu'il est beaucoup plus souple, facile à maintenir, et testable que l'utilisation de l'héritage.
Composition v/s l'Héritage est un vaste sujet. Il n'y a pas de véritable réponse pour ce qui est meilleur, car je pense que tout dépend de la conception du système.
Généralement le type de relation entre l'objet fournir de meilleures informations pour choisir l'un d'eux.
Si la relation de type "EST-UN" rapport puis l'Héritage est la meilleure approche.
autrement relation de type "EST-UN" de la relation, puis la composition à une meilleure approche.
Son totalement dépendent de l'entité-relation.
Que beaucoup de personnes ont dit, je vais d'abord commencer avec le check - s'il existe un "est-un" de la relation. S'il existe, j'ai l'habitude de vérifier les points suivants:
E. g 1. Comptable est un Employé. Mais je vais pas utiliser l'héritage, car un Employé de l'objet peut être instancié.
E. g 2. Livre est un SellingItem. Un SellingItem ne peut pas être instanciée - c'est un concept abstrait. Donc, je vais utiliser inheritacne. Le SellingItem est un classe de base abstraite (ou l'interface en C#)
Que pensez-vous de cette approche?
Aussi, je soutiens @anon réponse dans Pourquoi utiliser l'héritage à tous?
@MatthieuM. dit dans https://softwareengineering.stackexchange.com/questions/12439/code-smell-inheritance-abuse/12448#comment303759_12448
RÉFÉRENCE
Même si la Composition est préféré, je tiens à souligner les pros de Héritage et les inconvénients de Composition.
Pros de l'Héritage:
Il établit une logique "EST UN" relation. Si Voiture et Camion sont deux types de Véhicule ( classe de base), les enfants de la classe EST UN de la classe de base.
c'est à dire
Voiture est un Véhicule
Camion est un Véhicule
Avec l'héritage, vous pouvez définir/modifier/extension d'une capacité de
Les inconvénients de la Composition:
par exemple, Si Voiture contient Véhicule et si vous avez du prix de la Voiture, qui a été défini dans Véhicule, votre code sera comme ceci
Que voulez-vous la force de vous-même (ou un autre programmeur) à respecter et à quand voulez-vous permettre à vous-même (ou un autre programmeur) plus de liberté. Il a été soutenu que l'héritage est utile lorsque vous souhaitez forcer quelqu'un à une manière de gérer ou de résoudre un problème particulier, de sorte qu'ils ne peuvent pas la tête dans la mauvaise direction.
Is-a
etHas-a
est une règle générale.Je ne vois pas mentionné le diamant problème, qui peut survenir avec l'héritage.
En un coup d'œil, si les classes B et C héritent d'Un et de deux remplacer la méthode X, et une quatrième classe D hérite de B et C, et de ne pas remplacer X, dont la mise en œuvre de X D est supposé utiliser?
Wikipédia offre un bel aperçu du sujet traité dans cette question.
Cette règle est un non-sens complet. Pourquoi?
La raison en est que, dans tous les cas, il est possible de dire si l'utilisation de la composition ou de l'héritage.
Ceci est déterminé par la réponse à une question: "EST quelque chose d'Une autre chose" ou "A
quelque chose d'Une autre chose".
Vous ne pouvez pas "préfèrent" faire quelque chose pour quelque chose d'autre ou quelque chose d'autre. La logique stricte les règles s'appliquent.
Aussi il n'y a pas "inventé exemples" parce que, dans chaque situation, une réponse à cette question ne peut être donnée.
Si vous ne pouvez pas répondre à cette question il y a quelque chose de mal.
Cela comprend
le chevauchement des responsabilités de classess qui sont habituellement le résultat de de la mauvaise utilisation des interfaces, moins souvent par les réécrire le même code dans différents classess.
Pour éviter ces situations, je vous le recommande d'utiliser les bons noms de classes , que pleinement à leurs responsabilités.
Foo
dans unBar
*tout en conservant son identité en tant queBar
. Si pas, il suffit d'encapsuler unFoo
et de l'exposer en tant que membre.