DEV Community

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

Composition or Inheritance: What’s better?

Anthony Fung on April 26, 2023

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