Inheritance vs Composition - Fri, Aug 20, 2021
'Composition over Inheritance'
This is a phrase you will eventually hear at your workplace while coding away. Perhaps during lunch break or maybe in the comments of your code review. What exactly does this mean?
Well, in Object Oriented Programming, there really are two ways to relate two types to each other. ‘Is a’ vs ‘Has a’ is often the colloquial.
‘Is a’ implies inheritance. For example:
|
|
Implies Warrior
‘Is a’ Player
. Which is fine and all. But lets say now that we want to have rigorous unit testing, and our Player
Class has a lot of stuff going on already, perhaps a multitude of overridable methods and properties, and Warrior overrides a few.
Well, that is fine, but we will run into a problem. Take this for example:
|
|
At first glance, this method looks fine enough, perhaps it could be structured a bit better, but nothing seems to be terribly wrong with it. But we run our unit tests and, oh no! WarriorTests_AttackUndead
is failing after our changes earlier today!
Actually wait hold on, so is RogueTests_AttackUndead
, PriestTests_AttackLizard
and BarbarianTests_AttackFlying
, but other similar tests aren’t breaking on other character types, it seems kind of random and sporadic. What happened?
Well turns out it was this line of code:
|
|
See, CalculateAttack
is a method inherited from the Player
base class, and it had a weird bug in it.
But you were not able to instantly tell the source of the bug, because instead of a PlayerTests_Attack
test, you had a dozen various implementations with their own logic. Owch! Because you ended up making Player
an abstract class to be inherited from, you couldn’t easily unit test it, not without doing some extra boilerplate. Which is sub optimal…
So, what if instead, your Character
classes did this instead?
|
|
This is Composition vs Inheritance. And now what you have done is made Character
a normal class, and much easier to unit test.
And furthermore, now you can Mock an ICharacter
in your WarriorTests
, so even if Character
methods break, your WarriorTests
(and all others for that matter) will not throw up extra flags.
Instead only CharacterTests
will start breaking, making it very clear and concise exactly what broke and where, significantly decreasing debugging time.
Inheritance Isn’t all Bad Though
Because as it turns out, there is a clear cut time when you definitely want to inherit. The primary use case is Serialization
. Whether you have a RESTful interface serializing from JSON, Protobuff handling packets over sockets, or an ORM connected to a SQL DB manipulating entities, Inheritance is probably the right call for any Data
classes you are handling on these systems when needed.
Let’s consider the following class:
|
|
Sure, at first glance, this looks like an easy to unit test class that is loosely coupled to its Payload
. That’s all fine and dandy, until you try and Serialize
the darn thing! Since your trying to handle things via Injection
, many serializing libraries will scoff at your class and inform you it must have a parameterless constructor before they will even entertain the thought of working with the class. Doh!
In this case here, I would just use Inheritance
.
There are other examples of course, but realistically speaking this is the most common one I see. Too many people are far too willing to go all the way one direction or the other, claiming Inheritance vs Composition always does and will have a better choice.
But I disagree. There are times when one shines over the other and, like most things in programming, its all about using the right tool for the job.