[Language-agnostic] Préférez la composition sur l'héritage?


Answers

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

Pensez à l'héritage comme étant une 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 de la deuxième édition de Code Complete par Steve McConnell , Section 6.3 .

Question

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?




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

Ainsi, 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 : Reportez-vous à Modèles de conception d' State et de Strategy .

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




N'a pas trouvé une réponse satisfaisante ici, alors j'en ai écrit un nouveau.

Pour comprendre pourquoi « préférons la composition à l'héritage», nous devons d'abord revenir sur l'hypothèse omise dans cet idiome raccourci.

Il y a deux avantages de l'héritage: le sous-typage et le sous-classement

  1. Le sous-typage consiste à se conformer à une signature de type (interface), c'est-à-dire un ensemble d'API, et l'on peut surcharger une partie de la signature pour obtenir un polymorphisme de sous-typage.

  2. Le sous-classement signifie la réutilisation implicite des implémentations de méthodes.

Avec les deux avantages vient deux objectifs différents pour faire de l'héritage: le sous-typage orienté et la réutilisation du code orienté.

Si la réutilisation du code est le seul but, le sous-classement peut donner un de plus que ce dont il a besoin, c'est-à-dire que certaines méthodes publiques de la classe parente n'ont pas beaucoup de sens pour la classe enfant. Dans ce cas, au lieu de favoriser la composition sur l'héritage, la composition est exigée . C'est également de là que vient la notion «is-a» ou «has-a».

Ainsi, seulement lorsque le sous-typage est envisagé, c'est-à-dire pour utiliser la nouvelle classe plus tard d'une manière polymorphe, est-ce que nous sommes confrontés au problème du choix de l'héritage ou de la composition. C'est l'hypothèse qui est omise dans l'idiome raccourci en discussion.

Sous-type est de se conformer à une signature de type, ce qui signifie que la composition doit toujours exposer pas moins d'API du type. Maintenant, les compromis entrent en jeu:

  1. L'héritage fournit une réutilisation directe du code s'il n'est pas remplacé, tandis que la composition doit recoder chaque API, même s'il s'agit simplement d'un simple travail de délégation.

  2. L'héritage fournit une récursion ouverte directe via le site polymorphique interne, c'est-à-dire l'invocation de la méthode de remplacement (ou même du type ) dans une autre fonction membre, publique ou privée (bien que discouraged ). La récursivité ouverte peut être simulée via la composition , mais elle nécessite un effort supplémentaire et n'est pas toujours viable (?). Cette answer à une question dupliquée parle quelque chose de similaire.

  3. L'héritage expose les membres protégés . Cela interrompt 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 introduite.

  4. La composition a l'avantage d'une inversion de contrôle, et sa dépendance peut être injectée dynamiquement, comme le montre le motif décorateur et le modèle de proxy .

  5. La composition a l'avantage de combinator-oriented programmation combinator-oriented , c'est-à-dire de travailler de la même manière que le modèle composite .

  6. La composition suit immédiatement la programmation vers une interface .

  7. La composition a l'avantage de l' héritage multiple facile.

Avec les compromis ci-dessus à l'esprit, nous préférons donc la composition à l'héritage. Cependant, pour les classes étroitement liées, c'est-à-dire lorsque la réutilisation implicite du code apporte des bénéfices, ou que le pouvoir magique de la récursivité ouverte est désiré, l'héritage est le choix.




Aside from is a/has a considerations, one must also consider the "depth" of inheritance your object has to go through. Anything beyond five or six levels of inheritance deep might cause unexpected casting and boxing/unboxing problems, and in those cases it might be wise to compose your object instead.




Even though Composition is preferred, I would like to highlight pros of Inheritance and cons of Composition .

Pros of Inheritance:

  1. It establishes a logical " IS A" relation. If Car and Truck are two types of Vehicle ( base class), child class IS A base class.

    c'est à dire

    Car is a Vehicle

    Truck is a Vehicle

  2. With inheritance, you can define/modify/extend a capability

    1. Base class provides no implementation and sub-class has to override complete method (abstract) => You can implement a contract
    2. Base class provides default implementation and sub-class can change the behaviour => You can re-define contract
    3. Sub-class adds extension to base class implementation by calling super.methodName() as first statement => You can extend a contract
    4. Base class defines structure of the algorithm and sub-class will override a part of algorithm => You can implement Template_method without change in base class skeleton

Cons of Composition:

  1. In inheritance, subclass can directly invoke base class method even though it's not implementing base class method because of IS A relation. If you use composition, you have to add methods in container class to expose contained class API

eg If Car contains Vehicle and if you have to get price of the Car , which has been defined in Vehicle , your code will be like this

class Vehicle{
     protected double getPrice(){
          // return price
     }
} 

class Car{
     Vehicle vehicle;
     protected double getPrice(){
          return vehicle.getPrice();
     }
} 



To address this question from a different perspective for newer programmers:

Inheritance is often taught early when we learn object-oriented programming, so it's seen as an easy solution to a common problem.

I have three classes that all need some common functionality. So if I write a base class and have them all inherit from it, then they will all have that functionality and I'll only need to maintain it in once place.

It sounds great, but in practice it almost never, ever works, for one of several reasons:

  • We discover that there are some other functions that we want our classes to have. If the way that we add functionality to classes is through inheritance, we have to decide - do we add it to the existing base class, even though not every class that inherits from it needs that functionality? Do we create another base class? But what about classes that already inherit from the other base class?
  • We discover that for just one of the classes that inherits from our base class we want the base class to behave a little differently. So now we go back and tinker with our base class, maybe adding some virtual methods, or even worse, some code that says, "If I'm inherited type A, do this, but if I'm inherited type B, do that." That's bad for lots of reasons. One is that every time we change the base class, we're effectively changing every inherited class. So we're really changing class A, B, C, and D because we need a slightly different behavior in class A. As careful as we think we are, we might break one of those classes for reasons that have nothing to do with those classes.
  • We might know why we decided to make all of these classes inherit from each other, but it might not (probably won't) make sense to someone else who has to maintain our code. We might force them into a difficult choice - do I do something really ugly and messy to make the change I need (see the previous bullet point) or do I just rewrite a bunch of this.

In the end, we tie our code in some difficult knots and get no benefit whatsoever from it except that we get to say, "Cool, I learned about inheritance and now I used it." That's not meant to be condescending because we've all done it. But we all did it because no one told us not to.

As soon as someone explained "favor composition over inheritance" to me, I thought back over every time I tried to share functionality between classes using inheritance and realized that most of the time it didn't really work well.

The antidote is the Single Responsibility Principle . Think of it as a constraint. My class must do one thing. I must be able to give my class a name that somehow describes that one thing it does. (There are exceptions to everything, but absolute rules are sometimes better when we're learning.) It follows that I cannot write a base class called ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses . Whatever distinct functionality I need must be in its own class, and then other classes that need that functionality can depend on that class, not inherit from it.

At the risk of oversimplifying, that's composition - composing multiple classes to work together. And once we form that habit we find that it's much more flexible, maintainable, and testable than using inheritance.




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 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, la composition de l'objet est définie lors de l'exécution à travers des objets qui acquièrent 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 obligé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.







Lorsque vous voulez "copier" / Exposez l'API de la classe de base, vous utilisez l'héritage. Lorsque vous voulez seulement "copier" la fonctionnalité, utilisez la délégation.

Un exemple de ceci: Vous voulez créer une pile hors d'une liste. La pile n'a que pop, push et peek. Vous ne devriez pas utiliser l'héritage étant donné que vous ne voulez pas que push_back, push_front, removeAt, et al.-sorte de fonctionnalité dans une pile.




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.




I agree with @Pavel, when he says, there are places for composition and there are places for inheritance.

I think inheritance should be used if your answer is an affirmative to any of these questions.

  • Is your class part of a structure that benefits from polymorphism ? For example, if you had a Shape class, which declares a method called draw(), then we clearly need Circle and Square classes to be subclasses of Shape, so that their client classes would depend on Shape and not on specific subclasses.
  • Does your class need to re-use any high level interactions defined in another class ? The template method design pattern would be impossible to implement without inheritance. I believe all extensible frameworks use this pattern.

However, if your intention is purely that of code re-use, then composition most likely is a better design choice.




"Préférer la composition à l'héritage" est un principe de conception, qui dit ne pas abuser de l'héritage quand il ne rentre pas.

Si aucune relation hiérarchique réelle n'existe entre deux entités, n'utilisez pas l'héritage, mais utilisez plutôt la composition. La composition représente la relation "A A".

Par exemple, la voiture a une roue, un corps, un moteur, etc. Mais si vous héritez ici, cela devient CAR IS A Wheel - ce qui est incorrect.

Pour plus d'explications, reportez-vous à la section préférez la composition à l'héritage




Inheritance is a very powerfull machanism for code reuse. But needs to be used properly. I would say that inheritance is used correctly if the subclass is also a subtype of the parent class. As mentioned above, the Liskov Substitution Principle is the key point here.

Subclass is not the same as subtype. You might create subclasses that are not subtypes (and this is when you should use composition). To understand what a subtype is, lets start giving an explanation of what a type is.

When we say that the number 5 is of type integer, we are stating that 5 belongs to a set of possible values (as an example, see the possible values for the Java primitive types). We are also stating that there is a valid set of methods I can perform on the value like addition and subtraction. And finally we are stating that there are a set of properties that are always satisfied, for example, if I add the values 3 and 5, I will get 8 as a result.

To give another example, think about the abstract data types, Set of integers and List of integers, the values they can hold are restricted to integers. They both support a set of methods, like add(newValue) and size(). And they both have different properties (class invariant), Sets does not allow duplicates while List does allow duplicates (of course there are other properties that they both satisfy).

Subtype is also a type, which has a relation to another type, called parent type (or supertype). The subtype must satisfy the features (values, methods and properties) of the parent type. The relation means that in any context where the supertype is expected, it can be substitutable by a subtype, without affecting the behaviour of the execution. Let's go to see some code to exemplify what I'm saying. Suppose I write a List of integers (in some sort of pseudo language):

class List {
  data = new Array();

  Integer size() {
    return data.length;
  }

  add(Integer anInteger) {
    data[data.length] = anInteger;
  }
}

Then, I write the Set of integers as a subclass of the List of integers:

class Set, inheriting from: List {
  add(Integer anInteger) {
     if (data.notContains(anInteger)) {
       super.add(anInteger);
     }
  }
}

Our Set of integers class is a subclass of List of Integers, but is not a subtype, due to it is not satisfying all the features of the List class. The values, and the signature of the methods are satisfied but the properties are not. The behaviour of the add(Integer) method has been clearly changed, not preserving the properties of the parent type. Think from the point of view of the client of your classes. They might receive a Set of integers where a List of integers is expected. The client might want to add a value and get that value added to the List even if that value already exist in the List. But her wont get that behaviour if the value exists. A big suprise for her!

This is a classic example of an improper use of inheritance. Use composition in this case.

(a fragment from: use inheritance properly ).




L'héritage est très puissant, mais vous ne pouvez pas le forcer (voir: le problème cercle-ellipse ). Si vous ne pouvez vraiment pas être complètement sûr d'une vraie relation de sous-type "est-un", alors il est préférable d'aller avec la composition.




When you have an is-a relation between two classes (example dog is a canine), you go for inheritance.

On the other hand when you have has-a or some adjective relationship between two classes (student has courses) or (teacher studies courses), you chose composition.