“What’s better: composition or inheritance?”
I froze. Time stood still for me. I diverted my gaze leftward through a tall window in the room. The view wasn’t particularly inspiring: I could see the grey concrete wall of the office building across the street, complete with sparsely scattered windows. But at least it was a bright afternoon. That would calm my nerves.
I had previously heard of the phrase “prefer composition over inheritance”. Much like other words of wisdom, they had gone in without being properly digested. Trying to hide the blank look on my face, I did my best to deliver the most convincing answer I could.
It wasn’t enough. My interviewer saw right through the act and glanced down at his notes as I’m sure he’d done many times before with other candidates. “Composition”, he said as he calmly looked back up. “It gives more flexibility.”
Shortly after, I left the interview feeling tired. I had been on edge for the past three hours, trying to anticipate the questions I would be asked and being conscious of the signals my body language was sending. I nearly missed my train stop on the way home, and later received an email to confirm my suspicions: I didn’t get the job.
But I ended up with something more.
I had answers (or catalysts to answers) to a whole host of questions I didn’t know I had.
It would take a little while longer, but I eventually landed in a project where I was building a new API. I was designing models for request responses, and I noticed a few responses shared common data structures. “Ah – inheritance is great for reducing duplicated code” I though. After all, they were all variations of the same response. They would all come from the same API too. And so I built a hierarchy making use of inheritance. The result was a set of beautiful and clean data models.
After the feature was complete, I moved onto the next set of requirements. “Ok”, I thought. “We need to cater for a slight variation of the existing model set. That’s easily doable – I just need to tweak the responses”. Except I couldn’t. To achieve what I wanted, I would need multiple inheritance. This is something that isn’t possible with C# classes. At that moment I finally understood why composition offers more flexibility.
I chose inheritance when I originally designed the data models for two reasons:
To follow the DRY principle (an acronym for Don’t Repeat Yourself).
The models had a relationship with each other; they were different versions of the ‘same’ response.
On reflection, the models weren’t as tightly related as I had first thought, and the DRY principle could have been achieved by using composition. Let’s look at the latter point with a different example to better understand this concept.
Let’s imagine we want to model a hierarchy of vehicles. Among others, we want to include:
Let’s start off by having a
Vehicle base class.
Bicycles and trucks both travel on wheels, but the dimensions and number often differ between the two types of vehicle. As they both travel on the road, let’s make a
RoadVehicle class. This will subclass
Next, let’s think about what a truck and helicopter have in common. They both have an engine and use fuel. We can create a
MotorVehicle class to model this, again as a subclass of
When we come to add classes for our vehicles, we will come across the problem shown in Image 1.
A truck is both a road vehicle and a motor vehicle. However, C# does not allow multiple class inheritance; it cannot be modelled like this in our class hierarchy. We also cannot solve this by rearranging
MotorVehicle in the data structure:
MotorVehicleisn’t necessarily a
RoadVehicle: helicopters don’t travel on the road.
RoadVehicleisn’t necessarily a
MotorVehicle: bicycles typically don’t have a motor engine.
We could start introducing further subclasses such as
RoadMotorVehicle but this introduces problems itself:
More complexity is introduced into the model because of the additional classes.
Multiple inheritance isn’t allowed, so we’d have to copy over the
We can increase the flexibility of our model by using composition. Instead of creating the
MotorVehicle subclasses, let’s make
Helicopter direct subclasses of
Vehicle. Let’s also create new classes to capture information about a vehicle’s wheels and engine. By using these to compose our fully derived vehicle classes, we gain two benefits:
We won’t lose data when removing the
We won’t have to copy the wheel and engine related properties to the fully derived vehicle classes, thus respecting the DRY principle.
Image 2 shows the result of using composition in place of inheritance. It might seem like we are losing the benefits of polymorphism by streamlining the object model. However, we can get them back by creating interfaces that we can use to mark classes as having a wheelset, an engine, or both.
Composition and inheritance are two ways that you can use to build an object model. Both allow you to incorporate polymorphism – either through subclasses, or by interface implementation. Composition can offer more flexibility by allowing you to piece your models together with all the parts that you need. This could be more difficult (or impossible) when using inheritance because multiple class inheritance is not permitted in C#.
However, the semantics of each approach differs: each will give your model a different meaning. You shouldn’t automatically choose one by default because of its popularity or because people say so. It’s important that you use your unique knowledge of the system that you’re building to choose the best approach for your data.
Thanks for reading!
Level up your developer skills! Sign up for free for more articles like this, delivered straight to your inbox (link goes to Substack).