“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.
A problem with Inheritance
Let’s imagine we want to model a hierarchy of vehicles. Among others, we want to include:
A bicycle.
A truck.
A helicopter.
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 Vehicle
.
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 Vehicle
.
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 RoadVehicle
and MotorVehicle
in the data structure:
A
MotorVehicle
isn’t necessarily aRoadVehicle
: helicopters don’t travel on the road.A
RoadVehicle
isn’t necessarily aMotorVehicle
: 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
Fuel
andEngine
properties fromMotorVehicle
.
Modelling with Composition
We can increase the flexibility of our model by using composition. Instead of creating the RoadVehicle
and MotorVehicle
subclasses, let’s make Bicycle
, Truck
, and 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
RoadVehicle
andMotorVehicle
classes.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.
Summary
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).
Top comments (22)
Good subject, great intro and meaningful text. The example uses vehicles and this immediately shows why inheritance is risky. You can only use inheritance when you understand the concrete classes completely (a helicopter does have wheels, doesn't it?), now, but also for the (not always) forseeable future. And that is where most designs fail, it hardly ever happens that a designer understands the objects completely. I use inheritance a lot for interfaces, but hardly ever for data. (Ask yourself, is a square a special rectangle, or is a rectangle a special square?)
Ah yes. I had imagined the typical helicopter with only landing skids when I originally thought up the example. But you're right - there are some that have wheels too.
I also agree with you that only having a partial design is one of the biggest dangers when building an object model.
Obviously a square is a special 2 dimensional object with 4 90 degree corners and 4 sides of equal length. A rectangle is so common in comparison.
Hmm, I would not design like this.
I would:
Vehicle consists of Engine, chassis, trunk, compartment
Engine inherits to motor, human, fusion
Chassis inherits to ground, helicopter, fixedwing
Compartment can be directly used.
With this setup you use both. You use inheritance to abstract certain attributes. And you use composition to describe an object.
I think none is better. Just don't forget you have both tools available.
And if you want a bike just write:
Mybike = new vehicle(human (fit), ground(wheels(2), suspension(none), trunk(null)), compartment(passenger(1))
Mybike.drive(coordinates)
Ohh and about dry. Do not treat it as idiology. There are use cases where repetition may make sense. One thumb rule is the "reason for change".
If there are 2 independent sources that can change the code, you must at least prepare that they can diverge. In this case it is a valid approach to persist seperation and violating dry by doublicating code.
The reasoning behind is to reduce development complexity.
Completely agree.
Like many things, I had to make mistakes before I realised why many principles should be treated as guidelines only: once you learn the rules, you start to understand when to break them. I've managed to make quite a few mistakes and learn from them in the ~7 years since this event happened 😊
Hi Peter.
Nice abstraction - it certainly covers a lot and is much more comprehensive.
I was trying to come up with as simple an example as possible to illustration the point of being boxed-in should a new requirement mean an inheritance structure is no longer as relevant as when it was when designed.
I really do appreciate the input though - thanks.
Hi Anthony,
I think to come up with a design that keeps the application flexible, is hard work. Especially if you are coding under time pressure. And i am not convinced that replacing inheritance with composition has as much benefits as to think about how to get benefits from both approaches.
I appreciate your example because I have a hard time explaining what I believe is the way to go. So it helps me too. I gain clarity. :-)
I tend to use inheritance for services (that DO things) and composition for models (that ARE things). Not a blanket rule but it's served the majority of my requirements this far.
This is a good rule of thumb. I like to think about it this way: will I have to do the same operation on multiple different instances, especially in a loop? If yes, that is what Interfaces (or abstract base classes) are for.
If you only want to avoid duplicate code, but every different object will be used individually, then the composition is the way to go.
Modern C++ is trying to go a third path, using generative programming. See templates and concepts.
Sounds good - thanks for the insights Bojan!
Hi David
Sounds like a good approach. Would it be possible to give a quick example?
Apologies, Ive just seen this.
Essentially, I use composition for entitities/models and inheritance for actions/commands/queries (that can potentially be mocked for unit testing purposes).
So, using an example of a vehicle parts inventory system, a vehicle entity would be composed of smaller vehicle parts' entities (engine, braking system etc etc) which would in turn be composed of smaller enitites. Think of it as building (ie composing) the parent entity with a collection of smaller child entities.
Inheritence would be used to hide the implementation of actions carried out on the the vehicle entity. For example, I'd have a vehicle service which would consist of an vehicle interface. This would hide the implementation of all actions i can apply to a vehicle (ordering parts, retrieve specific info, update specs, etc etc). Inheritence lets me define the aplicable actions but hide thier implentaton (as per solid principles).
Hope that all makes sense (^_^)
So if I understand correctly,
Composition for models - like those build-it-yourself kits, where you can put e.g. a mini engine model, and mini seat models, inside of the truck model.
Inheritance for services - where you might call
ChangeTires
and it'd change two tires for a bike, and four for a car.Seems like a good rule of thumb!
Great example of using composition over inheritance. Also to the benefits you've outlined, diagram with inheritance looks more complex. And that complexety comes with no benefit.
I've used to use inheritance as the only hammer for all nails. Yet now I use composition more and more often.
Thanks Alex.
I used to use inheritance predominantly too - I think it's because my course lecturers emphasised it a bit more than they did composition when I learned to program.
They definitely each have their own specific use cases. I think we get better at judging the best technique the more we use them.
When interviewers ask these kind of questions, I tend to respond with something along the lines of: "Nothing in coding is black and white, except maybe the colors in your terminal".
Not even the terminal - that's black and green 😁
Jokes aside, I (would like to) think interviewers are assessing preferences or thinking styles, rather than expecting a specific answer. I'm guessing he gave me one of his answers because he could tell I hadn't really considered this question before at that point.
If someone who's done a lot of technical interviews we do look for thought process. Not all interviewers are equally skilled and so it's going to vary greatly.
One fallacy that appears frequently when talking to especially junior developers and even senior developers is the idea that different techniques are equally good, which I've found isn't true.
The goodness of particular technique, for example inheritance versus composition, is frequently measured by how many problems it causes for it's intended purpose.
Better techniques have a broader array of uses and fewer problems caused, as perceived by the user.
Inheritance, as a code sharing technique, doesn't see as much use because there are more places where it will cause problems than composition. I think the reason for that is that most things in the real world do not follow taxonomical structure. Eventually any described taxonomy that's not based on an actual taxonomy (e.g. taxonomy of life) will likely produce lots of mis-categorizations.
Thanks for the observations.
I suspect that "by being more flexible", my interviewer meant that it's less likely you'll get backed into a corner when new requirements come in. Whether he actually did, I'll never know.
My personal opinion is now to use the best tool for the job. Being more experienced with both techniques now helps. If I want to make a new custom exception, I'll subclass
Exception
. If I'm making a new custom object model, then it depends on many things, with how complete the requirements are being a factor.First sentence ... :D
I know - I originally planned for the headline to be Composition, Inheritance, and the Ghost of Interviews Past, but thought it'd come across vague. The word order changed too, to try to make it read and look better as a headline.
However, the opening quote is (approximately) as the question was asked. Or something like it - it happened a while ago and I don't have a perfect memory 😁