Composition over Inheritance đžđšī¸
Andrew Cairns
Posted on February 8, 2024
A lot can be learned about software development by playing retro games.
Video Version:
Tetris, for example, was a popular puzzle game back in the '80s. If you don't know what Tetris is (maybe you weren't born yet?!) let me quickly catch you up!
The whole point of the game is to complete as many lines as possible using a series of blocks that dropped from the top of the game board. Completing lines will clear the blocks from the board making space allowing you to play longer - and obtain a higher score.
It sounds easy, but let me tell you... it can get pretty intense when the board starts filling up with blocks and all you're trying to do is not freak out!
Now you may be thinking: "What can Tetris teach us about software development?"
In Tetris the whole goal of the game is to compose shapes together. You could say, the goal of the game is to create a new shape: a Complete Line, using existing shapes .
Likewise, in programming, we are often creating something new by utilising existing components. In both cases we combine existing components to create what we need.
This principle is called: Composition.
Composition
Composing objects together (which may be composed of other objects themselves) is a powerful way to build complex systems with a high degree of flexibility and modularity.
If we were to pretend our CompleteLine
was an object, we could say: "our completed line has four shapes".
Composition creates a "has a" relationship while inheritance is useful for modelling an "is a" relationship.
When we want to compose other objects together, just like shapes and Tetris, to achieve the functionality we want: this is Composition.
Inheritance
What can we learn about Inheritance from retro games?
Let's think about... Mario!
In most Mario games, when Mario obtains a Power Up (such as a Fire Flower or a Super Mushroom) he gains a specific ability or enhancement.
However, he can only have one power up at a time.
This limitation of a single Power Up mirrors the restrictions of Single Inheritance.
In some programming languages a class can only inherit from one superclass at a time.
This creates a hierarchy where subclasses inherit the properties and behaviours of only a single parent class.
As we mentioned when explaining Composition.
Inheritance creates an "is a" relationship.
Super Mario is a Mario.
Fire Mario is a Mario.
And so on.
Inheritance is when we design our types around what they are - and can be an intuitive way to model a simple system.
So, how come people often say we should: "Favour Composition over Inheritance?"
The main arguments for choosing Composition over Inheritance primarily revolve around the maintainability and flexibility of a system once it starts to grow.
Separation is not Organisation
A reason often stated why we should prefer Composition over Inheritance is the ability to promote code reuse. However, I don't believe Composition should be the reason we separate code.
Scott Yu-Jan describes this perfectly in the intro to one of his videos:
They just kinda divide up your stuff into smaller units of mess.
If we considered composition as being the driving factor for creating smaller units of code, we would end up with the programming equivalent of a drawer organiser.
Would we have smaller units of code? Yes.
Would we still have a mess? Yes!
It's for this reason I don't believe Composition to be a good driving factor for code reuse.
Instead I believe it's following the Single Responsibility Principle that helps us build smaller units that can later be composed together. Smaller units of code that adhere to the Single Responsibility Principle are easier to maintain and easier to update. They are more flexible because they were designed to do one thing and do that one thing well!
Composition's role is to help us take advantage of these existing smaller units of code - not define them! It's what allows us to build more complex functionality in ways inheritance can't.
Inheritance is also one of the strongest possible types of coupling. Changes to a base class can inadvertently affect the functionality of its child classes.
How often does this happen?
Well, it happens often enough to be called: The Fragile Base Class Problem.
The Fragile Base Class Problem
Let's explore what this means by using the ghosts from PacMan.
Imagine a ghost base class with four other ghost subclasses. If we were to introduce a change to the ghost base class - all subclasses would be impacted.
Composition however would provide us with Object Containment - reducing the impact of changes.
Instead of using Inheritance to reuse code, we can use Dependency Injection and utilise Composition instead. We also spoke about about Single Responsibility earlier, too.
That's two of the five SOLID principles.
I'll be covering the SOLID principles in more depth in future content, but for now - seems like it's game over for inheritance, right?
Well...
Would I advise you to favour Composition over Inheritance?
Yeah, I would - because of all the reasons mentioned above. But not all inheritance is bad and we should not blindly avoid it. Composition and Inheritance are just tools.
Instead, I love this perspective from Mathias Verraes on defining heuristics on when, and when not, to use Inheritance:
Posted on February 8, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.