145 votes

Héritage et agrégation

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 :

  1. Héritage : étendre la fonctionnalité 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 agnostique quant à son implémentation.

  2. Agrégation : créer une nouvelle fonctionnalité en prenant d'autres classes et en les combinant en une nouvelle classe. Attachez une interface commune à cette nouvelle classe pour assurer 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 ?

Ce débat revient régulièrement sur le tapis, mais je ne pense pas qu'il ait été posé sur le site de la Commission européenne. Stack Overflow (bien qu'il y ait des discussions à ce sujet). Il y a également un manque surprenant de bons résultats sur Google à ce sujet.

174voto

Toon Krijthe Points 36327

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'un héritage ou d'une agrégation.

  • Si la nouvelle classe est plus ou moins comme la classe d'origine. Utilisez l'héritage. La nouvelle classe est maintenant une sous-classe de la classe d'origine.
  • Si la nouvelle classe doit ont la classe originale. Utilisez l'agrégation. La nouvelle classe a maintenant la classe originale comme membre.

Cependant, il y a une grande zone grise. Nous avons donc besoin de plusieurs autres astuces.

  • Si nous avons utilisé l'héritage (ou si nous prévoyons de l'utiliser) mais que nous n'utilisons qu'une partie de l'interface, ou si nous sommes obligés de remplacer un grand nombre de fonctionnalités pour que la corrélation reste logique. Nous avons alors une grosse odeur désagréable qui indique que nous devions utiliser l'agrégation.
  • Si nous avons utilisé l'agrégation (ou si nous prévoyons de l'utiliser) mais que nous découvrons que nous devons copier presque toutes les fonctionnalités. Nous avons alors une odeur qui pointe dans la direction de l'héritage.

Pour faire court. Nous devons 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 ne devons 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é, dans le cas où nous avons une classe qui a besoin d'une partie de la fonctionnalité de la classe originale, est de diviser la classe originale en une classe racine et une sous-classe. Et laisser la nouvelle classe hériter de la classe racine. Mais il faut faire attention à ne pas créer une séparation illogique.

Ajoutons un exemple. Nous avons une classe "Dog" avec les méthodes suivantes : "Eat", "Walk", "Bark", "Play".

class Dog
  Eat;
  Walk;
  Bark;
  Play;
end;

Nous avons maintenant besoin d'une classe 'Cat', qui a besoin de 'Eat', 'Walk', 'Purr', et 'Play'. Essayez d'abord de l'étendre à partir d'un chien.

class Cat is Dog
  Purr; 
end;

Ça a l'air, d'accord, mais attendez. Ce chat peut aboyer (les amoureux des chats vont me tuer pour ça). Et un chat qui aboie viole les principes de l'univers. Donc nous devons remplacer la méthode Bark pour qu'elle ne fasse rien.

class Cat is Dog
  Purr; 
  Bark = null;
end;

Ok, ça marche, mais ça sent mauvais. Alors essayons une agrégation :

class Cat
  has Dog;
  Eat = Dog.Eat;
  Walk = Dog.Walk;
  Play = Dog.Play;
  Purr;
end;

Ok, c'est sympa. Ce chat n'aboie plus, il n'est même plus silencieux. Mais il a toujours un chien interne qui veut sortir. Alors essayons la solution numéro trois :

class Pet
  Eat;
  Walk;
  Play;
end;

class Dog is Pet
  Bark;
end;

class Cat is Pet
  Purr;
end;

C'est beaucoup plus propre. Pas de chiens internes. Et les chats et les chiens sont au même niveau. On peut même introduire d'autres animaux domestiques pour étendre le modèle. Sauf si c'est un poisson, ou quelque chose qui ne marche pas. Dans ce cas, nous devons à nouveau procéder à un remaniement. Mais ce sera pour une autre fois.

36voto

toolkit Points 27248

Au début de l'année GOF ils déclarent

Favoriser la composition d'objets plutôt que l'héritage de classes.

Ce point est examiné plus en détail ici

26voto

Harper Shelby Points 13395

La différence est généralement exprimée comme la différence entre "est un" et "a un". L'héritage, c'est-à-dire la relation "est un", est bien résumé par la formule suivante Principe de substitution de Liskov . L'agrégation, la relation "a", est juste cela - elle 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é en termes de", qui peut également être modélisée par l'agrégation d'objets membres (non exposés).

14voto

Craig Walker Points 13478

Voici mon argument le plus courant :

Dans tout système orienté objet, une classe comporte deux parties :

  1. Son interface : la "face publique" de l'objet. C'est l'ensemble des capacités qu'il annonce au reste du monde. Dans beaucoup de langages, cet ensemble est bien défini dans une "classe". Habituellement, ce sont les signatures des méthodes de l'objet, bien que cela varie un peu selon les langues.

  2. Son mise en œuvre : le travail "en coulisse" que l'objet effectue pour satisfaire son interface et fournir une fonctionnalité. Il s'agit généralement du code et des données membres de l'objet.

L'un des principes fondamentaux de la POO est que l'implémentation est encapsulé (c'est-à-dire : caché) à l'intérieur de la classe ; la seule chose que les personnes extérieures doivent voir est l'interface.

Lorsqu'une sous-classe hérite d'une sous-classe, elle hérite généralement de les deux l'implémentation et l'interface. Cela signifie donc que vous êtes forcé pour accepter les deux comme des contraintes sur votre classe.

Avec l'agrégation, vous pouvez choisir soit l'implémentation, soit l'interface, soit les deux, mais vous n'êtes pas obligé de choisir l'une ou l'autre. La fonctionnalité d'un objet est laissée à l'objet lui-même. Il peut s'en remettre à d'autres objets comme il le souhaite, mais il est en fin de compte responsable de lui-même. D'après mon expérience, cela conduit à un système plus flexible, plus facile à modifier.

Ainsi, lorsque je développe un logiciel orienté objet, je préfère presque toujours l'agrégation à l'héritage.

11voto

Bill Karwin Points 204877

J'ai donné une réponse à "Is a" vs "Has a" : lequel est le meilleur ? .

En fait, je suis d'accord avec d'autres personnes : n'utilisez l'héritage que si votre classe dérivée est vraiment est le type que vous étendez, et pas seulement parce qu'il contient les mêmes données. N'oubliez pas que l'héritage signifie que la sous-classe acquiert 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 bien vous promettez-vous tranquillement que ces méthodes doivent être ignorées dans la classe dérivée ? Ou bien vous trouvez-vous en train de remplacer des méthodes de la superclasse, en les rendant inopérantes pour que personne ne les appelle par inadvertance ? Ou bien vous donnez des indications à votre outil de génération de documents API pour qu'il omette la méthode dans le document ?

Ce sont des indices forts qui montrent que l'agrégation est le meilleur choix dans ce cas.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X