Il existe deux écoles de pensée sur la meilleure façon d'étendre, d'améliorer et de réutiliser le code dans un système orienté objet:
Héritage: étendez les fonctionnalités d'une classe en créant une sous-classe. Remplacez les membres de la superclasse dans les sous-classes pour fournir de nouvelles fonctionnalités. Rendre les méthodes abstraites / virtuelles pour forcer les sous-classes à "remplir les blancs" lorsque la superclasse veut une interface particulière mais est indépendante de son implémentation.
Agrégation: créez de nouvelles fonctionnalités en prenant d'autres classes et en les combinant dans une nouvelle classe. Attachez une interface commune à cette nouvelle classe pour l'interopérabilité avec d'autres codes.
Quels sont les avantages, les coûts et les conséquences de chacun? Existe-t-il d'autres alternatives?
Je vois ce débat venir régulièrement, mais je ne pense pas qu'il ait encore été demandé sur Stack Overflow (bien qu'il y ait une discussion connexe). Il y a aussi un manque surprenant de bons résultats Google pour cela.
la source
Réponses:
Il ne s'agit pas de savoir lequel est le meilleur, mais de savoir quand utiliser quoi.
Dans les cas «normaux», une simple question suffit pour savoir si nous avons besoin d'héritage ou d'agrégation.
Cependant, il y a une grande zone grise. Nous avons donc besoin de plusieurs autres astuces.
Pour couper court. Nous devrions utiliser l'agrégation si une partie de l'interface n'est pas utilisée ou doit être modifiée pour éviter une situation illogique. Nous n'avons besoin d'utiliser l'héritage que si nous avons besoin de presque toutes les fonctionnalités sans changements majeurs. Et en cas de doute, utilisez l'agrégation.
Une autre possibilité pour, le cas où nous avons une classe qui a besoin d'une partie des fonctionnalités de la classe d'origine, est de diviser la classe d'origine en une classe racine et une sous-classe. Et laissez la nouvelle classe hériter de la classe racine. Mais vous devez faire attention à cela, ne pas créer une séparation illogique.
Ajoutons un exemple. Nous avons une classe «Chien» avec des méthodes: «Eat», «Walk», «Bark», «Play».
Nous avons maintenant besoin d'une classe «Chat», qui a besoin de «Mange», «Marche», «Ronronne» et «Joue». Alors essayez d'abord de l'étendre à partir d'un chien.
Regarde, d'accord, mais attendez. Ce chat peut aboyer (les amoureux des chats me tueront pour ça). Et un chat qui aboie viole les principes de l'univers. Nous devons donc remplacer la méthode Bark afin qu'elle ne fasse rien.
Ok, ça marche, mais ça sent mauvais. Alors essayons une agrégation:
Ok, c'est bien. Ce chat n'aboie plus, pas même silencieux. Mais il a toujours un chien interne qui veut sortir. Essayons donc la solution numéro trois:
C'est beaucoup plus propre. Pas de chiens internes. Et les chats et les chiens sont au même niveau. Nous pouvons même introduire d'autres animaux pour étendre le modèle. À moins que ce ne soit un poisson ou quelque chose qui ne marche pas. Dans ce cas, nous devons à nouveau refactoriser. Mais c'est quelque chose pour une autre fois.
la source
makeSound
et laisserais chaque sous-classe implémenter son propre type de son. Cela vous aidera ensuite dans les situations où vous avez beaucoup d'animaux de compagnie et que vous le faitesfor_each pet in the list of pets, pet.makeSound
.Au début de GOF, ils déclarent
Ceci est discuté plus en détail ici
la source
La différence est généralement exprimée comme la différence entre «est un» et «a un». L'héritage, la relation «est une», se résume bien dans le principe de substitution de Liskov . L'agrégation, la relation «a une», n'est que cela - cela montre que l'objet d'agrégation a un des objets agrégés.
D'autres distinctions existent également - l'héritage privé en C ++ indique une relation «est implémentée en termes de», qui peut également être modélisée par l'agrégation d'objets membres (non exposés).
la source
Voici mon argument le plus courant:
Dans tout système orienté objet, il y a deux parties à toute classe:
Son interface : le "visage public" de l'objet. C'est l'ensemble des capacités qu'il annonce au reste du monde. Dans de nombreux langages, l'ensemble est bien défini dans une "classe". Ce sont généralement les signatures de méthode de l'objet, bien que cela varie un peu selon la langue.
Sa mise en œuvre : le travail «en coulisse» que fait l'objet pour satisfaire son interface et lui apporter des fonctionnalités. Il s'agit généralement du code et des données de membre de l'objet.
L'un des principes fondamentaux de la POO est que l'implémentation est encapsulée (c'est-à-dire cachée) dans la classe; la seule chose que les étrangers devraient voir est l'interface.
Lorsqu'une sous-classe hérite d'une sous-classe, elle hérite généralement à la fois de l'implémentation et de l'interface. Cela, à son tour, signifie que vous êtes forcé d'accepter les deux comme des contraintes pour votre classe.
Avec l'agrégation, vous pouvez choisir l'implémentation ou l'interface, ou les deux, mais vous n'êtes pas obligé de le faire non plus. La fonctionnalité d'un objet est laissée à l'objet lui-même. Il peut s'en remettre à d'autres objets comme il l'entend, mais il est finalement responsable de lui-même. D'après mon expérience, cela conduit à un système plus flexible: un système plus facile à modifier.
Ainsi, chaque fois que je développe un logiciel orienté objet, je préfère presque toujours l'agrégation à l'héritage.
la source
J'ai donné une réponse à "Est-ce qu'un" vs "A un": lequel est le meilleur? .
Fondamentalement, je suis d'accord avec les autres: n'utilisez l'héritage que si votre classe dérivée vraiment est le type que vous étendez, pas simplement parce qu'elle contient les mêmes données. N'oubliez pas que l'héritage signifie que la sous-classe obtient les méthodes ainsi que les données.
Est-il logique que votre classe dérivée ait toutes les méthodes de la superclasse? Ou vous promettez-vous tranquillement que ces méthodes doivent être ignorées dans la classe dérivée? Ou vous trouvez-vous en train de remplacer les méthodes de la superclasse, ce qui en fait des non-opérations pour que personne ne les appelle par inadvertance? Ou donner des conseils à votre outil de génération de doc API pour omettre la méthode du document?
Ce sont des indices forts que l'agrégation est le meilleur choix dans ce cas.
la source
Je vois beaucoup de réponses «is-a vs has-a; ils sont conceptuellement différents» sur cette question et les questions connexes.
La seule chose que j'ai trouvée dans mon expérience est qu'essayer de déterminer si une relation est «est-un» ou «a-a» est voué à l'échec. Même si vous pouvez effectuer correctement cette détermination pour les objets maintenant, les exigences changeantes signifient que vous vous tromperez probablement à un moment donné dans le futur.
Une autre chose que j'ai trouvée est que c'est très difficile de de l'héritage à l'agrégation une fois qu'il y a beaucoup de code écrit autour d'une hiérarchie d'héritage. Le simple passage d'une superclasse à une interface signifie changer presque toutes les sous-classes du système.
Et, comme je l'ai mentionné ailleurs dans cet article, l'agrégation a tendance à être moins flexible que l'héritage.
Ainsi, vous avez une tempête parfaite d'arguments contre l'héritage chaque fois que vous devez choisir l'un ou l'autre:
Ainsi, j'ai tendance à choisir l'agrégation - même lorsqu'il semble y avoir une forte relation is-a.
la source
La question est normalement formulée comme Composition vs Héritage , et elle a déjà été posée ici.
la source
Je voulais faire un commentaire sur la question originale, mais 300 caractères mordent [; <).
Je pense que nous devons être prudents. Premièrement, il y a plus de saveurs que les deux exemples assez spécifiques faits dans la question.
De plus, je suggère qu'il est utile de ne pas confondre l'objectif avec l'instrument. On veut s'assurer que la technique ou la méthodologie choisie soutient la réalisation de l'objectif principal, mais je ne pense pas que la discussion hors contexte sur quelle technique est la meilleure soit très utile. Il est utile de connaître les pièges des différentes approches ainsi que leurs points forts clairs.
Par exemple, que comptez-vous accomplir, de quoi disposez-vous pour commencer et quelles sont les contraintes?
Créez-vous un framework de composants, même spécifique? Les interfaces sont-elles séparables des implémentations dans le système de programmation ou est-ce réalisé par une pratique utilisant un autre type de technologie? Pouvez-vous séparer la structure d'héritage des interfaces (le cas échéant) de la structure d'héritage des classes qui les implémentent? Est-il important de masquer la structure de classe d'une implémentation du code qui repose sur les interfaces fournies par l'implémentation? Y a-t-il plusieurs implémentations utilisables en même temps ou la variation est-elle plus au fil du temps en raison de la maintenance et de l'amélioration? Ceci et bien d'autres doivent être pris en compte avant de vous fixer sur un outil ou une méthodologie.
Enfin, est-il si important de verrouiller les distinctions dans l'abstraction et comment vous y pensez (comme dans is-a versus has-a) aux différentes caractéristiques de la technologie OO? Peut-être que oui, si cela maintient la structure conceptuelle cohérente et gérable pour vous et les autres. Mais il est sage de ne pas être asservi par cela et les contorsions que vous pourriez finir par faire. Il est peut-être préférable de prendre du recul et de ne pas être aussi rigide (mais laissez une bonne narration pour que les autres puissent dire ce qui se passe). [Je cherche ce qui rend une partie particulière d'un programme explicable, mais parfois je privilégie l'élégance quand il y a une plus grande victoire. Ce n’est pas toujours la meilleure idée.]
Je suis un puriste d'interface et je suis attiré par les types de problèmes et d'approches où le purisme d'interface est approprié, qu'il s'agisse de créer un cadre Java ou d'organiser certaines implémentations COM. Cela ne le rend pas approprié pour tout, pas même près de tout, même si je ne jure que par cela. (J'ai quelques projets qui semblent fournir de sérieux contre-exemples contre le purisme d'interface, il sera donc intéressant de voir comment j'arrive à faire face.)
la source
Je couvrirai la partie où-ces-pourraient-s'appliquer. Voici un exemple des deux, dans un scénario de jeu. Supposons qu'il existe un jeu qui a différents types de soldats. Chaque soldat peut avoir un sac à dos qui peut contenir différentes choses.
L'héritage ici? Il y a un béret vert marin et un tireur d'élite. Ce sont des types de soldats. Donc, il y a un soldat de classe de base avec Marine, Béret vert et Sniper comme classes dérivées
Agrégation ici? Le sac à dos peut contenir des grenades, des fusils (différents types), un couteau, un medikit, etc. Un soldat peut être équipé de n'importe lequel de ces éléments à tout moment, et il peut également avoir un gilet pare-balles qui agit comme une armure lorsqu'il les blessures diminuent jusqu'à un certain pourcentage. La classe de soldat contient un objet de classe de gilet pare-balles et la classe de sac à dos qui contient des références à ces éléments.
la source
Je pense que ce n'est pas un débat entre les deux. C'est juste ça:
Les deux ont leur place, mais l'héritage est plus risqué.
Bien sûr, cela n'aurait pas de sens d'avoir une classe Shape 'ayant-un' Point et une classe Square. Ici l'héritage est dû.
Les gens ont tendance à penser d'abord à l'héritage lorsqu'ils essaient de concevoir quelque chose d'extensible, c'est ce qui ne va pas.
la source
La faveur se produit lorsque les deux candidats se qualifient. A et B sont des options et vous préférez A. La raison en est que la composition offre plus de possibilités d'extension / flexiblité que la généralisation. Cette extension / flexibilité se réfère principalement à la flexibilité d'exécution / dynamique.
L'avantage n'est pas immédiatement visible. Pour voir l'avantage, vous devez attendre la prochaine demande de modification inattendue. Ainsi, dans la plupart des cas, ceux qui sont restés fidèles à la généralisation échouent par rapport à ceux qui ont adopté la composition (sauf un cas évident mentionné plus loin). D'où la règle. D'un point de vue d'apprentissage, si vous pouvez implémenter une injection de dépendances avec succès, vous devez savoir lequel privilégier et quand. La règle vous aide également à prendre une décision; si vous n'êtes pas sûr, sélectionnez la composition.
Résumé: Composition: Le couplage est réduit en ayant simplement des choses plus petites que vous branchez à quelque chose de plus grand, et le plus gros objet rappelle simplement le plus petit objet. Génération: du point de vue de l'API, définir qu'une méthode peut être remplacée est un engagement plus fort que de définir qu'une méthode peut être appelée. (très peu d'occasions lorsque la généralisation l'emporte). Et n'oubliez jamais qu'avec la composition, vous utilisez aussi l'héritage, à partir d'une interface au lieu d'une grande classe
la source
Les deux approches sont utilisées pour résoudre différents problèmes. Vous n'avez pas toujours besoin d'agréger plus de deux classes ou plus lors de l'héritage d'une classe.
Parfois, vous devez agréger une seule classe parce que cette classe est scellée ou a des membres non virtuels que vous devez intercepter afin de créer une couche proxy qui n'est évidemment pas valide en termes d'héritage, mais tant que la classe que vous utilisez par proxy a une interface à laquelle vous pouvez vous abonner, cela peut fonctionner assez bien.
la source