DEV Community

Cover image for Composition or Inheritance: What’s better?
Anthony Fung
Anthony Fung

Posted on • Originally published at webdeveloperdiary.substack.com

Composition or Inheritance: What’s better?

“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.

Image 1: A Truck cannot be a subclass of both RoadVehicle and MotorVehicle

Image 1: A Truck cannot be a subclass of both RoadVehicle and MotorVehicle

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 a RoadVehicle: helicopters don’t travel on the road.

  • A RoadVehicle isn’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 Fuel and Engine properties from MotorVehicle.

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 and MotorVehicle 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.

Image 2: An updated object model using composition instead of inheritance

Image 2: An updated object model using composition instead of inheritance

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)

Collapse
 
jhelberg profile image
Joost Helberg

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?)

Collapse
 
ant_f_dev profile image
Anthony Fung

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.

Collapse
 
brense profile image
Rense Bakker

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.

Collapse
 
leginee profile image
Peter

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)

Collapse
 
leginee profile image
Peter

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.

Collapse
 
ant_f_dev profile image
Anthony Fung

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 😊

Collapse
 
ant_f_dev profile image
Anthony Fung

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.

Collapse
 
leginee profile image
Peter

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. :-)

Collapse
 
dyloneus profile image
Dave A

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.

Collapse
 
hrnkas profile image
Bojan Hrnkas

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.

Collapse
 
ant_f_dev profile image
Anthony Fung

Sounds good - thanks for the insights Bojan!

Collapse
 
ant_f_dev profile image
Anthony Fung

Hi David

Sounds like a good approach. Would it be possible to give a quick example?

Collapse
 
dyloneus profile image
Dave A

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 (^_^)

Thread Thread
 
ant_f_dev profile image
Anthony Fung • Edited

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!

Collapse
 
kondrashov profile image
Alex Kondrashov

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.

Collapse
 
ant_f_dev profile image
Anthony Fung

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.

Collapse
 
brense profile image
Rense Bakker

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".

Collapse
 
ant_f_dev profile image
Anthony Fung

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.

Collapse
 
oadrian2 profile image
oadrian2

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.

Thread Thread
 
ant_f_dev profile image
Anthony Fung

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.

Collapse
 
polterguy profile image
Thomas Hansen

First sentence ... :D

Collapse
 
ant_f_dev profile image
Anthony Fung

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 😁