In object oriented design, inheritance can be a good thing. It has many uses and, in particular, it is almost essential for certain design patterns.
However, inheritance can also be a bad thing. Get it wrong, and your code can end up in a mess. So, we say, as a general rule, prefer composition over inheritance. But what does that mean?
Before answering that, let's define when it is appropriate to consider inheritance.
One of the common heuristics is the is a? and has a? rule. With this heuristic, we only consider inheritance if the is a? rule is applicable. For example, if we are considering whether
Van should inherit
Vehicle, we might say 'a van is a vehicle' and be satisfied. But what if our domain has no need for
Vehicle and instead we're using
Van as a specialized container for moving stock? Should
StockContainer? Let's apply the heuristic: 'a van is a stock container'. That seems to be OK. However, there is a problem. Let's try the has a? heuristic: 'a van has a stock container'. Hmm, that sort of makes sense too if you consider the storage compartment separately. The problem is that our spoken language is heavily nuanced and is not always reliable when used to design software.
When we say 'is a' in the test above, what we should really mean 'is always and never not a'; and when we say 'has a', we should mean 'has, or has the behaviour of, a'. Let's try that out:
- A van is always and never not a vehicle. That seems reasonable.
- A van is always and never not a stock container. That does not sound right.
- A van has, or has the behaviour of, a stock container. That's better.
So we might consider
Vehicle, but we certainly should not consider
StockContainer; and use composition here instead.
But, sometimes, it's even more subtle than that. Consider the following statements:
- Barney is a dog.
- Barney is a labrador.
- A dog in an animal.
- Labrador is a breed.
- Barney is an animal.
- Barney is a breed.
Clearly, the last statement is incorrect. 'Barney' is not a breed. The above example provides us with another heuristic, which is to try walking further up the inheritance chain.
If we hadn't taken this step, we may have modeled the above using inheritance and ended up with the following:
class Animal class Dog < Animal class Labrador < Dog barney = Labrador.new
And we would probably end up regretting it. So what does 'Labrador is a breed' tell us about how to design our objects?
It suggests that the attributes associated with breed should be factored out into a separate class:
class Breed attr_accessor :size def name end def colours end def traits end end class Labrador < Breed NAME = 'Labrador' def name NAME end def colours [:gold, :chocolate, :black] end def traits [:friendly] end end
And we can then use composition to share these with
barney = Dog.new(Labrador.new)
Or perhaps with a factory method:
barney = Dog.create(:labrador)
We might then access these through a
breed getter method:
In a strongly typed language such as Java or C#, we can further improve this by defining these behaviours on an interface, e.g.
IBreed, implement that interface in
Dog as well and delegate it to
If we are using a loosely-typed language such as Ruby, then we might add some of the
Breed methods to
Dog and delegate these to