"Prefer composition over inheritance" is just a good heuristic
You should consider the context, as this is not a universal rule. Do not take it to mean never use inheritance when you can do composition. If that were the case, you would fix it by banning inheritance.
I hope to make this point cleared along this post.
I will not try to defend the merits of composition by itself. Which I consider off topic. Instead, I will talk about some situations when developer may consider inheritance that would be better using composition. On that note, inheritance has its own merits, which I also consider off topic.
Vehicle example
I am writing about developers who tries to things the dumb way, for narrative purposes
Let us go for a variant of the classical examples that some OOP courses use… We have a Vehicle
class, then we derive Car
, Airplane
, Balloon
and Ship
.
Note: If you need to ground this example, pretend these are kinds of objects in a video game.
Then Car
and Airplane
may have some common code, because they both can roll on wheel on land. The developers may consider creating an intermediary class in the inheritance chain for that. Yet, actually there is also some shared code between Airplane
and Balloon
. They could consider creating another intermediary class in the inheritance chain for that.
Thus, the developer would be looking for multiple inheritance. At the point where the developers are looking for multiple inheritance, the design has already gone wrong.
It is better to model this behavior as interfaces and composition, so we can reuse it without having to run into multiple class inheritance. If the developers, for example, create the FlyingVehicule
class. They would be saying that Airplane
is a FlyingVehicule
(class inheritance), but we could instead say that Airplane
has a Flying
component (composition) and Airplane
is a IFlyingVehicule
(interface inheritance).
Using interfaces, if necessary, we can have multiple inheritance (of interfaces). In addition, you are not coupling to a particular implementation. Increasing reusability and testability of your code.
Remember that inheritance is a tool for polymorphism. In addition, polymorphism is a tool for reusability. If you can increase the reusability of your code by using composition, then do so. If you are not sure whatever or not composition provides better reusability, "Prefer composition over inheritance" is a good heuristic.
All that without mentioning Amphibious
.
In fact, we may not need things that go off the ground. Stephen Hurn has a more eloquent example in his articles “Favor Composition Over Inheritance” part 1 and part 2.
Substitutability and Encapsulation
Should A
inherit or compose B
?
If A
Is an specialization of B
that should fulfil the Liskov substitution principle, inheritance is viable, even desirable. If there are situations where A
is not a valid substitution for B
then we should not use inheritance.
We might be interested in composition as a form of defensive programming, to defend the derived class. In particular, once you start using B
for other different purposes, there may be pressure to change or extend it to be more suitable for those purposes. If there is the risk that B
may expose methods that could result in an invalid state in A
we should be using composition instead of inheritance. Even if we are the author of both B
and A
, it is one thing less to worry about, therefore composition eases the reusability of B
.
We may even argue that if there are features in B
that A
does not need (and we do not know if those features could result in an invalid state for A
, either in the present implementation or in the future), it is a good idea to use composition instead of inheritance.
Composition also has the advantages of allowing switching implementations, and easing mocking.
Note: there are situations where we want to use composition despite the substitution being valid. We archive that substitutability by using interfaces or abstract classes (which one to use when is another topic) and then use composition with dependency injection of the real implementation.
Finally, of course, there is the argument that we should use composition to defend the parent class because inheritance breaks the encapsulation of the parent class:
Inheritance exposes a subclass to details of its parent's implementation, it's often said that 'inheritance breaks encapsulation'
-- Design Patterns: Elements of Reusable Object-Oriented Software, Gang of Four
Well, that is a poorly designed parent class. Which is why you should:
Design for inheritance, or prohibit it.
-- Effective Java, Josh Bloch
Yo-Yo problem
Another case where composition helps is the Yo-Yo problem. This is a quote from Wikipedia:
In software development, the yo-yo problem is an anti-pattern that occurs when a programmer has to read and understand a program whose inheritance graph is so long and complicated that the programmer has to keep flipping between many different class definitions in order to follow the control flow of the program.
You can resolve, for example: Your class C
will not inherit from class B
. Instead your class C
will have a member of type A
, which may or may not be (or have) an object of type B
. This way you will not be programming against the implementation detail of B
, but against the contract that the interface (of) A
offers.
Counter examples
Many frameworks favor inheritance over composition (which is the opposite of what we have been arguing). Developer may do this because they put a lot of work into their base class that having it implemented with composition would have increase the size of the client code. Sometimes this is due to limitations of the language.
For example, a PHP ORM framework may create a base class that uses magic methods to allow writing code as if the object had real properties. Instead the code handled by the magic methods will be going to the database, query for the particular requested field (perhaps cache it for future request) and return it. Doing it with composition would require the client to either create properties for each field or write some version of the magic methods code.
Addendum: There some other ways one could allow extending ORM objects. Thus, I do not think inheritance is necessary on this case. It is cheaper.
For another example, a video game engine may create a base class that uses native code depending on the target platform to do 3D rendering and event handling. This code is complex and platform specific. It would be expensive and error prone for the developer user of the engine to deal with this code, in fact, that is part of the reason of using the engine.
Furthermore, without the 3D rendering part, this is how many widget frameworks work. This releases you from worrying about handling OS messages… in fact, in many languages you cannot write such code without some form of native biding. Moreover, if you were to do it, it would tight your portability. Instead, with inheritance, provided the developer do not break compatibility (too much); you will be able to easily port your code to any new platforms they support in the future.
In addition, consider that many times we only want to override a few methods and leave everything else with the default implementations. Had we been using composition we would have to create all those methods, even if only to delegate to the wrapped object.
By this argument, there is a point where composition can be worst for maintainability than inheritance (when the base class is too complex). Yet, remember that maintainability of inheritance can be worse than that of composition (when the inheritance tree is too complex), which is what I refer to in the yo-yo problem.
In the presented examples, the developers rarely intend to reuse the code generated via inheritance in other projects. That mitigates the diminished reusability of using inheritance instead of composition. In addition, by using inheritance the framework developers can provide a lot of easy to use and easy to discover code.
Final thoughts
As you can see, composition has some advantage over inheritance in some situations, not always. It is important to consider the context and the different factors involved (such as reusability, maintainability, testability, etc…) to make the decision. Back to the first point: "Prefer composition over inheritance" is a just good heuristic.
You may also have notices that many of the situation I describe can be resolved with Traits or Mixins to some degree. Sadly, these are not common features in the great list of languages, and they usually come with some performance cost. Thankfully, their popular cousin Extension Methods and Default Implementations mitigate some situations.
I have a recent post where I talk about some of the advantages of interfaces in why do we require interfaces between UI,Business and Data access in C#. It helps decoupling and eases reusability and testability, you might be interested.