Question Préférez la composition sur l'héritage?


Pourquoi préférer la composition à l'héritage? Quels compromis y a-t-il pour chaque approche? Quand devriez-vous choisir l'héritage plutôt que la composition?


1348
2017-09-08 01:58


origine


Réponses:


Préférer la composition sur l'héritage car elle est plus malléable / facile à modifier plus tard, mais n'utilise pas une approche composer-toujours. Avec la composition, il est facile de modifier le comportement à la volée avec Dependency Injection / Setters. L'héritage est plus rigide car la plupart des langues ne vous permettent pas de dériver de plus d'un type. Donc l'oie est plus ou moins cuite une fois que vous dérivez de TypeA.

Mon test d'acidité pour ce qui précède est:

  • Est-ce que TypeB veut exposer l'interface complète (toutes les méthodes publiques pas moins) de TypeA tel que TypeB peut être utilisé où TypeA est prévu? Indique Héritage.

par exemple. Un biplan Cessna exposera l'interface complète d'un avion, sinon plus. Donc, cela le rend apte à dériver de l'avion.

  • Est-ce que TypeB ne veut qu'une partie / une partie du comportement exposé par TypeA? Indique le besoin de Composition. 

par exemple. Un oiseau peut n'avoir besoin que du comportement de vol d'un avion. Dans ce cas, il est logique de l'extraire en tant qu'interface / classe / les deux et en faire un membre des deux classes.

Mettre à jour: Je viens de revenir à ma réponse et il semble maintenant que c'est incomplet sans une mention spécifique de Barbara Liskov Principe de substitution de Liskov comme un test pour «Dois-je hériter de ce type?


1010
2017-09-10 03:04



Pensez à l'endiguement en tant que a un relation. Une voiture "a un" moteur, une personne "a un" nom ", etc.

Pensez à l'héritage en tant que est un relation. Une voiture est un véhicule, une personne est un mammifère, etc.

Je ne prends aucun crédit pour cette approche. Je l'ai pris directement à partir du Deuxième édition du code terminée par Steve McConnell, Section 6.3.


347
2017-09-08 02:09



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 PHP5). Toute la logique est codée dans un ensemble de fonctions. Vous pouvez inclure d'autres fichiers contenant des fonctions auxiliaires, etc. et exécuter votre logique métier en transmettant des données dans les fonctions. Cela peut être très difficile à gérer à mesure que l'application se développe. PHP5 tente d'y remédier en offrant plus de design orienté objet.

Héritage

Cela encourage l'utilisation des classes. L'héritage est l'un des trois principes de la conception OO (héritage, polymorphisme, encapsulation).

class Person {
   String Title;
   String Name;
   Int Age
}

class Employee : Person {
   Int Salary;
   String Title;
}

C'est l'héritage au travail. L'employé "est une" personne ou hérite de la personne. Toutes les relations d'héritage sont des relations "is-a". Employé ombrage également la propriété Titre de la personne, ce qui signifie Employee.Title retournera le titre pour l'employé et non la personne.

Composition

La composition est favorisée par rapport à l'héritage. Pour le dire très simplement, vous auriez:

class Person {
   String Title;
   String Name;
   Int Age;

   public Person(String title, String name, String age) {
      this.Title = title;
      this.Name = name;
      this.Age = age;
   }

}

class Employee {
   Int Salary;
   private Person person;

   public Employee(Person p, Int salary) {
       this.person = p;
       this.Salary = salary;
   }
}

Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);

La composition est typiquement "a un" ou "utilise un" relation. Ici, la classe Employee a une personne. Il n'hérite pas de Person mais reçoit à la place l'objet Person qui lui est passé, ce qui explique pourquoi il "a une" Person.

Composition sur héritage

Maintenant, dites que vous voulez créer un type de gestionnaire afin de vous retrouver avec:

class Manager : Person, Employee {
   ...
}

Cet exemple fonctionnera bien, cependant, que si la personne et l'employé ont déclaré Title? Manager.Title doit-il renvoyer "Manager of Operations" ou "Mr."? Sous la composition cette ambiguïté est mieux gérée:

Class Manager {
   public Title;
   public Manager(Person p, Employee e)
   {
      this.Title = e.Title;
   }
}

L'objet Manager est composé d'un employé et d'une personne. Le comportement du titre provient de l'employé. Cette composition explicite supprime l'ambiguïté entre autres choses et vous rencontrerez moins de bugs.


175
2018-05-21 07:54



Avec tous les avantages indéniables apportés par l'héritage, voici quelques-uns de ses inconvénients.

Inconvénients de l'héritage:

  1. Vous ne pouvez pas modifier l'implémentation héritée des super classes lors de l'exécution (évidemment, car l'héritage est défini lors de la compilation).
  2. Héritage expose une sous-classe aux détails de l'implémentation de la classe de son parent, c'est pourquoi on dit souvent que l'héritage brise l'encapsulation (dans le sens où vous devez vraiment vous concentrer sur les interfaces, pas sur l'implémentation).
  3. Le couplage étroit fourni par l'héritage rend l'implémentation d'une sous-classe très liée à l'implémentation d'une super classe que toute modification de l'implémentation parente force la sous-classe à changer.
  4. Une réutilisation excessive par sous-classement peut rendre la pile d'héritage très profonde et très confuse aussi.

D'autre part Composition de l'objet est défini au moment de l'exécution à travers des objets acquérant des références à d'autres objets. Dans un tel cas, ces objets ne pourront jamais atteindre les données protégées les uns des autres (pas de rupture d'encapsulation) et seront forcés de respecter l'interface de l'autre. Et dans ce cas également, les dépendances d'implémentation seront beaucoup moins importantes qu'en cas d'héritage.


91
2018-05-21 08:34



Une autre raison, très pragmatique, de préférer la composition à l'héritage concerne le modèle de votre domaine et le mappage vers une base de données relationnelle. Il est vraiment difficile de mapper l'héritage au modèle SQL (vous vous retrouvez avec toutes sortes de solutions de contournement hacky, comme la création de colonnes qui ne sont pas toujours utilisées, l'utilisation de vues, etc.). Certains ORML tentent de gérer cela, mais cela se complique rapidement. La composition peut être facilement modélisée par une relation de clé étrangère entre deux tables, mais l'héritage est beaucoup plus difficile.


77
2017-09-08 02:48



En bref, je serais d'accord avec "Préférer la composition sur l'héritage", très souvent pour moi, ça sonne comme "préfèrent les pommes de terre au coca-cola". Il y a des lieux d'héritage et des lieux de composition. Vous devez comprendre la différence, alors cette question disparaîtra. Ce que cela signifie vraiment pour moi, c'est "si vous allez utiliser l'héritage - détrompez-vous, il y a de fortes chances que vous ayez besoin de la composition".

Vous devriez préférer les pommes de terre au coca-cola quand vous voulez manger, et le coca-cola aux pommes de terre quand vous voulez boire.

Créer une sous-classe devrait signifier plus qu'un simple moyen pratique d'appeler des méthodes de super-classe. Vous devriez utiliser l'héritage quand la sous-classe "est-a" super classe à la fois structurellement et fonctionnellement, quand elle peut être utilisée comme super-classe et que vous allez l'utiliser. Si ce n'est pas le cas - ce n'est pas l'héritage, mais quelque chose d'autre. La composition est quand vos objets se composent d'un autre, ou a une certaine relation avec eux.

Donc, pour moi, il semble que si quelqu'un ne sait pas s'il a besoin d'héritage ou de composition, le vrai problème est qu'il ne sait pas s'il veut boire ou manger. Pensez à votre domaine de problème plus, mieux le comprendre.


64
2018-05-08 22:29



L'héritage est assez attrayant, surtout en ce qui concerne les procédures et il semble souvent trompeusement élégant. Je veux dire que tout ce que je dois faire est d'ajouter cette fonctionnalité à un autre cours, n'est-ce pas? Eh bien, l'un des problèmes est que

l'héritage est probablement la pire forme de couplage que vous pouvez avoir

Votre classe de base rompt l'encapsulation en exposant les détails d'implémentation aux sous-classes sous la forme de membres protégés. Cela rend votre système rigide et fragile. Le défaut le plus tragique est cependant la nouvelle sous-classe apporte avec elle tout le bagage et l'opinion de la chaîne de l'héritage.

L'article, L'héritage est le mal: l'échec épique de la DataAnnotationsModelBinder, parcourt un exemple de ceci en C #. Il montre l'utilisation de l'héritage quand la composition aurait dû être utilisée et comment elle pourrait être refactorisée.


54
2018-01-23 19:55



En Java ou en C #, un objet ne peut pas changer de type une fois qu'il a été instancié.

Donc, si votre objet doit apparaître comme un objet différent ou se comporter différemment en fonction de l'état ou des conditions d'un objet, utilisez Composition: Faire référence à Etat et Stratégie Modèles de conception.

Si l'objet doit être du même type, utilisez Héritage ou implémenter des interfaces.


37
2017-09-08 02:25