Composition over Inheritance

Video script:

Today, we are are going to talk about composition over inheritance. Inheritance is when you design your types after what they are, while composition is when you design your types after what they cando. We are going to learn about the limitations of inheritance, and how to solve them using composition.

This is a weekly show where we try to become more confident and excited about programming by learning intriguing things that we didn’t know before. A tip is that you’ll get more out of this video if you’ve watched the previous episode about factory functions.

Let’s first talk about inheritance for a bit. Let’s say that we are designing a game, and I need a Dog:

Dog
.bark()

After a while, we realize that our software, like everything, needs Cats, so we create a Cat class:

Cat
.meow()

Because nature calls, we add .poop() to the Cat and the Dog class:

Dog
.poop()
.bark()
Cat
.poop()
.meow()

That’s duplication, so we lift .poop() into a shared Animal class.

Animal
.poop()
   Dog
.bark()
   Cat
.meow()

Now that we have a lot of animals pooping everywhere, we need a cleaningrobot:

CleaningRobot
.drive()
.clean()

You also need a MurderRobot that can .drive() and .kill() the Cats and Dogs that are .poop()ing all over your white floors:

MurderRobot
.drive()
.kill()

Since .drive() is now duplicated between CleaningRobot and MurderRobot we create a Robot class to put it in.

Robot
.drive()
   CleaningRobot
.clean()
   MurderRobot
.kill()

This is what the whole shabang looks like:

Robot
.drive()
    CleaningRobot
.clean()
    MurderRobot
.kill()
Animal
.poop()
    Dog
.bark()
    Cat
.meow()

A couple of months of development go by, and your Dog, MurderRobot and friends have all grown into a mature, stable system. You’re feeling good about it.

But at this point in the project, without fail, the project manager will say this:

“Our customers demand a MurderRobotDog. It needs to be able to .kill(), .drive(), .bark(), but it cannot poop().

And now, we’re screwed. We simply cannot fit the MurderRobotDog nicely into this inheritance hierarchy. We could create a new parent object, where you put all the functionality that is shared:

GameObject
.bark()
    Robot
.drive()
        CleaningRobot
.clean()
        MurderRobot
.kill()
          MurderRobotDog
    Animal
.poop()
        Dog
        Cat
.meow()

But that means that your objects will have a ton of functionality that they don’t use, so you end up with a Gorilla/Banana problem — you request a banana, but you end up with a gorilla holding the banana and the entire jungle with it.

The other bad route that you can take is to duplicate functionality:

Robot
.drive()
    CleaningRobot
.clean()
    MurderRobot
.kill()
      MurderRobotDog
.bark()
Animal
.poop()
    Dog
.bark()
    Cat
.meow()

That’s not as bad, but it’s still yucky duplication.

Composition to the rescue! Again, inheritance is when you design your types around what they are, and composition is when you design types around what they do.

So, using composition, we would do this instead:

dog            = pooper + barker
cat = pooper + meower
cleaningRobot = driver + cleaner
murderRobot = driver + killer
murderRobotDog = driver + killer + barker

I’m going to show you how to do this in practice using JavaScript. We are going to do this using normal functions and normal object literals.

const barker = (state) => ({
bark: () => console.log('Woof, I am ' + state.name)
})
const driver = (state) => ({
drive: () => state.position = state.position + state.speed
})

These are the definitions of barker and driver. (Host has temporary been replaced with elevator music) You might have noted that these functions are like the factories from previous video, but instead of creating their own state internally, they accept their state as function parameter.

const murderRobotDog = (name)  => {
let state = {
name,
speed: 100,
position: 0
}
return Object.assign(
{},
barker(state),
driver(state),
killer(state)
)
}
const bruno =  murderRobotDog('bruno')
bruno.bark() // "Woof, I am Bruno"

So here we have the murderRobotDog factory function. First, it creates a state object. It assigns some default values to it — speed and position, and also assigns the name from the funciton argument. It then uses something called Object.assign — this is a function that is built into ES6, but there are equivalent methods in all JavaScript libraries. What it does is that it takes an object, in this case a new, empty object, and assign the properties from other objects to it.

So in this case, it creates a barker, a driver, a killer, and then merge them all into the new object, and return it. Hence, we have our MurderRobotDog. Easy as pie.

So we’ve looked at an example of an inheritance tree that broke down, and then we looked at how to restructure it using composition, and we looked at how to compose objects with JavaScript.

The question that is probably on your mind now is — when to use each one? Well… the vast majority of developers agree that we should favor composition over inheritance. Me, personally, I don’t see the point of inheritiance at all. I don’t think it is a good pattern, and I don’t think it should be used.

A lot of people are going to tell you that if something has a “is a” relationship, then you should use inheritance. For example, Mattias “is a” man, thus I can inherit man. If the relationship is of a “has a” nature, such as a car “has an” engine, then you should use composition.

I think that reasoning is topsy-turvy. While I am a man, I also have arms, and have cooking skills. While the car “has an engine”, it is also a vehicle. You can interpret pretty much any concept both ways.

But the really big problem with inheritance is that you’re encouraged to predict the future. Inheritance encourages you to build this taxonomy of objects very early on in your project, and you are most likely going to make design mistakes doing that, because humans cannot predict the future (even though it feels like we can), and getting out of these inheritiance taxonomies is a lot harder than getting out of them.

I think it’s just better to use composition in the first place. It’s more flexible, powerful, and it’s also very easy to do, so why not.

And that’s it, that’s my thoughts on composition. But what are yours? Comment down below, or just like the video if you’re not the talkative type.

Again, in this show, we try to become more confident and excited about programming by learning intriguing things that we didn’t know before. There won’t be an episode next monday, because I will be at JSConf in Berlin. If you’re going to be there, Tweet me @mpjme so that we can say hi in real life. If you’re not going to JSConf, make sure that you subscribe so that you don’t miss the next episode.

Until next time, stay curious.