📜 ⬆️ ⬇️

Composition vs inheritance

Like all developers, I often had to read and hear the statement that "composition is always better than inheritance." Probably too often. However, I am not inclined to take anything for granted, so let's see if this is true.


So what are the advantages of composition over inheritance?

1. There is no name conflict possible with inheritance.
2. The ability to change the aggregated object at runtime.
3. Complete replacement of the aggregated object in classes derived from the class that includes the aggregated object.
')
In the latter two cases, it is highly desirable that the interchangeable objects have a common interface. And in the third, the method returning such an object is virtual.

If we consider, for example, C #, which does not support multiple inheritance, but allows us to inherit from a variety of interfaces and create extension methods for these interfaces, then we can distinguish two more advantages (in this case, we can only talk about behaviors ( algorithms ) within the “Strategy "):
4. Aggregated behavior (algorithm) may include other objects. What in particular makes it possible to reuse other behavior through aggregation.
5. During aggregation, it is possible to hide a certain part of the implementation, as well as the initial parameters necessary for the behavior, by passing them through the constructor (when inheriting, the behavior will have to be requested through the methods / properties of the own interface).

But what about the cons? Are they not?

1. So, if we need the ability to change behavior from the outside, then composition, as compared with inheritance, has a fundamentally different type of relationship between the object of behavior and the object using it. If, when inheriting from abstract behavior, we have a 1: 1 ratio, then with aggregation and the possibility of setting behavior from the outside, we obtain a 1: many ratio. Those. the same behavior object can be used by several owner objects. This creates problems with the state of behavior common to several such owner-sites.

You can resolve this situation by banning the installation of behavior from outside or entrusting it, for example, with a generic method:
void SetBehavior<TBehavior>() 
thereby prohibiting the creation of behavior by anyone other than the owner object. However, we cannot prohibit the use of behavior "somewhere else." In languages ​​without a garbage collector (GC), this creates understandable problems. Of course, in such languages ​​you can wrongfully refer to the owner object itself, but by distributing the separated objects of behavior to the right and left, we get many times more chances to get an exception.

2. Aggregation (and this is, perhaps, the main nuance) differs from inheritance in the first place in that the aggregated object is not the owner object and does not contain information about it. There are often situations when the code itself, the owner object itself, is also necessary for the code interacting with the behavior (for example, to obtain information about what other behaviors it possesses).

In this case, we have to either transfer an untyped object (such as object or void *) to such a code, or create an additional interface for the owner object (some IBehaviorOwner), or store a circular reference to the owner object in the behavior. It is clear that each of these options has its disadvantages and further complicates the code. Moreover, different types of behaviors may depend on each other (and this is quite acceptable, especially if they are in some kind of closed self-sufficient module).

3. Well, the last minus is of course performance. If there are a lot of owner objects, the creation and destruction of two or more objects instead of one object may not go unnoticed.

It turns out that the statement “composition is always better than inheritance” is controversial in some cases and should not be a dogma. This is especially true of languages ​​that allow multiple inheritance and do not have GC. If in any situation the advantages listed above are not important, and it is known in advance that you will not be able to use them when working with certain types, you should still consider the option of inheritance.

Source: https://habr.com/ru/post/177447/


All Articles