StreamVehicle example
I will take the exampleI 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 StreamVehicle
class. This class allows you to read, then we derive Car
, Airplane
, Balloon
and write byte arrays from itShip
.
Note: If you want to provide the ability to read and write custom struct without losing your abilityneed to read and write byte arrays, you may consider inheriting from it. If every developer doesground this example, it will lead to different classes to do different operations on thepretend these are kinds of objects in a video game.
Then StreamCar
, and no way to reconcile them… if a developer wants to do operation XAirplane
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 provided byalso some shared code between StreamXAirplane
and operation Y
that is provided by StreamYBalloon
. They could consider creating another intermediary class in the inheritance chain for that.
Thus, the developer will startwould be looking for multiple inheritance. When that happensAt the point where the developers are looking for multiple inheritance, thingsthe design has already gone wrong.
Therefore, inheritance is not a good way to extend behavior. It would beis better to provide some form of utility methods. If we plan to use it to extendmodel this behavior as interfaces and statecomposition, so we should consider composition.
Let us say you did usecan reuse it without having to run into multiple class inheritance anyway. Now you have no way to decouple your code from If the developers, for example, create the StreamFlyingVehicule
implementationclass. Disregarding the case whereThey would be saying that StreamAirplane
changes, this can still cause trouble. In particular, if you want to test your code without creating an actualis a StreamFlyingVehicule
, you cannot mock it, because it is in your (class inheritance chain.
Sometimes when people say "Prefer composition over inheritance", they actually mean to use composition and other things… For example), if I create classbut we could instead say that uses a StreamAirplane
member, I have lost the ability to replacehas a SteamFlying
object with one of mine. Given the situation, it can be better to use ancomponent IStream
member(composition) and implement IStreamAirplane
. Where is a IStreamIFlyingVehicule
is an interface※(interface inheritance). This way
Using interfaces, if necessary, we can have multiple inheritance of(of interfaces is possible). In addition, you are not coupling to a particular implementation. Increasing reusability and testability of your code.
※: This also allows having multiple types of IStream
. For example, one could read from a network socket, another from file system, and another could just store in memory. I am aware this could also work with an abstract class. As I said earlier, inheritance has its own merits. This is about situations that are better with composition.
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 instead of inheritance, 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.
Vehicule exampleSubstitutability and Encapsulation
Let us go for a variant of the classical examples that some OOP courses use… We have aShould VehicleA
class, then we deriveinherit or compose CarB
,?
If AirplaneA
, Is an specialization of BalloonB
andthat should fulfil the Ship
Liskov substitution principle, inheritance is viable, even desirable. ThenIf there are situations where CarA
andis not a valid substitution for AirplaneB
may have some common codethen we should not use inheritance.
We might be interested in composition as a form of defensive programming, because they both can roll on wheel on landto defend the derived class. WeIn particular, once you start using B
for other different purposes, there may consider creating an intermediary class in the inheritance chainbe pressure to change or extend it to be more suitable for thatthose purposes. Yet, actuallyIf there is also some shared code betweenthe risk that AirplaneB
andmay expose methods that could result in an invalid state in BalloonA
we should be using composition instead of inheritance.
Note: If you need to ground this example, pretend these Even if we are kindsthe author of objects in a video game.
It is better to model this behavior as interfacesboth B
and compositionA
, so we can reuse it without havingis one thing less to run into multiple class inheritance. If we, for exampleworry about, createtherefore composition eases the reusability of FlyingVehiculeB
class.
We would be sayingmay even argue that if there are features in AirplaneB
is athat FlyingVehiculeA
does not need (class inheritance), butand we do not know if those features could also say that Airplane
has aresult in an invalid state for FlyingA
component (composition, either in the present implementation or in the future) and Airplane
, 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 IFlyingVehicule
substitutability by using interfaces or abstract classes (interface inheritancewhich one to use when is another topic) and then use composition with dependency injection of the real implementation.
AllFinally, of course, there is the argument that without mentioningwe should use composition Amphibious
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
You can resolve this by using a form of the adapter pattern. Which is again the same situation I have been talking about, 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.
AddendumAddendum: There some other ways one could allow extending ORM objects. Thus, I do not think inheritance is necessary on this case. It is cheaper.
In addition of what I said above, 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.