Composition vs inheritance

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

So, what are the advantages of a composition over inheritance?

1. There is no name conflict that is possible with inheritance.
2. The ability to change the aggregated object in runtime.
3. Complete replacement of the aggregated object in classes derived from the class including the aggregated object.

In the last two cases, it is highly desirable that replaceable aggregate objects have a common interface. And in the third - so that the method returning such an object is virtual.

If we consider, for example, C #, which does not support multiple inheritance, but allows you to inherit from multiple interfaces and create extension methods for these interfaces, then there are two more advantages (in this case we can only talk about behaviors ( algorithms ) in the framework of the “Strategy” pattern »):
4. Aggregated behavior (algorithm) may include other objects. Which, in particular, allows reusing 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 (during inheritance, the behavior will have to be requested through the methods / properties of its own interface).

But what about the cons? Are they really gone?

1. So, if we need the ability to change behavior from the outside, then composition, in comparison 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 get a 1: many ratio. Those. the same behavior object can be used by several owner objects. This causes problems with a common behavior state for several of these owner objects.

This situation can be resolved by prohibiting the installation of behavior from the outside or by entrusting it, for example, to the generic method:
void SetBehavior()
thus prohibiting the creation of behavior by anyone other than the owner object. However, we cannot prohibit the use of the behavior “elsewhere”. In languages ​​without a garbage collector (GC), this causes understandable problems. Of course, in such languages, you can also unlawfully refer to the owner object itself, but by distributing separated behavior objects to the right and left, we get many times more chances to get an exception.

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

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

3. Well, the last minus is of course performance. If there are a lot of objects-owners, then 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 for languages ​​that allow multiple inheritance and do not have a GC. If in any situation the above advantages are not important, and it is known in advance that when working with certain types you will not be able to use them, you should still consider the option of inheritance.

Also popular now: