Contents

Composition over inheritance in Javascript

In object oriented programming (OOP), according to the composition over inheritance principle, code should be re-used by composing classes containing instances of other classes that implement the desired functionality, rather than inheritance from a base or parent class.

What’s wrong about inheritance?

One of the biggest problems about inheritance is that it creates a hierarchy of classes really hard to maintain when it starts to grow.

Let me show you what I mean by that.

Example

Let’s say we have to represent some pets like cats and dogs.

A typical approach to this would be to create a parent class Pet with common properties and methods that cat and dog will inherit, so we would end up with something like the following structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Pet:
    - name
    - eat()
    - run()

        Dog:
            - bark()

        Cat:
            - meow()

Everything seems fine until the day you are asked to introduce a lion, because it will happen! Lions are wild animals thus they don’t usually have a name nor an owner so unless we create a new objet with duplicated methods we must update the structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Animal:
    - eat()
    - run()
    - hunt()

        Pet:
            - name

                Dog:
                    - bark()

                Cat:
                    - meow()

        Lion:
            - roar()

Ok we saved the day and we feel good. But the very next day here comes a new requirement: our application needs pet owners. Pet owners can eat and run but they are not animals (let’s assume so).

So we could create a super-object that wraps common methods as we did earlier but things start getting ugly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Being:
    - name
    - eat()
    - run()

    PetOwner
        - email
        - adopt()
        - talk()

    Animal:
        - hunt()

            Pet:
                - owner

                    Dog:
                        - bark()

                    Cat:
                        - meow()

            Lion:
                - roar()

… and so on …

So here is the problem, with the time and new requirements coming, we will be forced to either introduce duplications or constantly creating new super-classes also called God classes re-arranging their properties and methods.

Both this is far from being ideal because on one hand nobody likes duplications and on the other you will eventually bump into the problem of the gorilla and the banana…

You ask for a banana but you get a gorilla holding the banana and the entire jungle.

Composition to rescue

Composition is a design pattern that addresses this problem.

The key is trying to think in terms of what the objects do rather than what they are.

In other words we compose our objects specifying what they can do.

With this new mind set lets see how we could re-design our previous objects.

1
2
3
4
Pet owner: can eat, can run, can adopt
Cat: can eat, can run, can meow
Dog: can eat, can run, can bark
Lion: can eat, can run, can hunt, can roar

Now that we have defined what our objects are able to do we can create these abilities using some factory functions

Let’s see it in practice

Let's create the abilities
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
const canEat = state => ({
    eat: () => console.log(`I love eating ${state.food}`)
})

const canRun = state => ({
    run: () => console.log(`I'm running hard`)
})

const canAdopt = state => ({
    adopt: () => console.log(`I adopted ${state.pet}`)
})

const canBark = state => ({
    bark: () => console.log(`Woof! I'm ${state.name} and ${state.owner.name} is my master.`)
})

const canTalk = state => ({
    talk: () => console.log(`Hi! I'm ${state.name}`)
})

...

Now, instead of extending a super-class we can create factories that combine the required abilities and return objects with their state and methods.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const createPetOwner = (name, email) => {
    const state = {
        name,
        email
    }

    return {
        ...state,
        ...canEat(state),
        ...canRun(state),
        ...canAdopt(state),
        ...canTalk(state)
    }
}

const createDog = (name, owner) => {
    const state = {
        name,
        owner
    }

    return {
        ...state,
        ...canEat(state),
        ...canRun(state),
        ...canBark(state)
    }
}

...

We can now create pet owners, dogs etc… and use their abilities.

1
2
3
4
5
fabrizio = createPetOwner('Fabrizio', 'example@example.com')
bingo = createDog('Bingo', fabrizio)

console.log(fabrizio.talk()) // Hi! I'm Fabrizio
console.log(bingo.bark()) // Woof! I'm Bingo and Fabrizio is my

Should an object require new abilities or different sub-sets of them, simply create the new abilities and compose your object factories accordingly.

Conclusion

Should inheritance be abandoned for good?

Generally speaking I would tend to prefer composition in any case but as always it depends on the context.

As rule of thumb I would use inheritance only when I’m sure my objects won’t change and I can design them in a manner to have 1 or 2 levels of inheritance tops.

Composition unchain you from fragile and tightly coupled inheritance structures and helps up avoid what we are worse at: predicting the future.