One of the most repeated pieces of advice in Object Oriented Design is "prefer composition over inheritance." But what do we get by using composition over inheritance? What makes it so compelling that most of the OO evangelists favor composition over inheritance?

Difference

First, let's define both terms to know exactly what to expect of each, and what do they bring to the table. Inheritance is when we design our classes and objects around what they are, while in composition is when we plan our classes and objects around what they do.

In an inheritance-based solution, a given object often has multiple responsibilities it has inherited from its various ancestors. Composition-based solutions break up a problem into distinct responsibilities and encapsulate the implementation of each into separate objects.

Therefore, composition has more flexibility to build behavior dynamically because the various components get combined at run-time to create the desired behavior.

Understanding the Inheritance Limitation

Let's say we are working on a Star Wars game. We started by defining a Padawan class, which has a applyBasicForceSkills() and handleLightsaber() methods. Then we need another class for Master Jedies that has applySuperForceSkills() and also a handleLightsaber() method.

Master
    applySuperForceSkills()
    handleLightsaber()

Padawan
    applyBasicForceSkills()
    handleLightsaber()

We noticed the duplication in the design above because it is hard to miss, and we decided to create a class/object on top of those two and have the handleLightsaber() there.

Jedi
    handleLightsaber()
    
    Master
        applySuperForceSkills()

    Padawan
        applyBasicForceSkills()

Everything seems fine. Now, we need some XWingFighters able to fly() and shoot(). Then, we need Stormtroopers who can shoot() and, do what they are really great at, missTarget().

XWingFighter
    fly()
    shoot()

Stormtrooper
    missTarget()
    shoot()

Again, the shoot() method could be on a father class and the XWingFighter and Stormtrooper will extend that class/object.

Troopers
    shoot()

    XWingFighter
        fly()

    Stormtrooper
        missTarget()

We ended up with the following in our system.

Jedi
    handleLightsaber()
    
    Master
        applySuperForceSkills()

    Padawan
        applyBasicForceSkills()

Troopers
    shoot()

    XWingFighter
        fly()

    Stormtrooper
        missTarget()

A new requirement was requested. We needed to make it possible for the Jedi Master to be able to fly() a ship, but should not be able to shoot(). We can't fit that into our current design.

One thing we can do is to add a fly() method to the Master class, but we know this is a bad practice. We would be duplicating the functionality. Here we would notice that the composition would be much better than the inheritance

Solving with Composition

Solving the problem above could be achievable using the composition. We define our classes/objects around what they do. For instance, Shooter, Flyer, LightsaberHandler, SuperForceApplier, BasicForceApplier, TargetMisser...etc. Then, we combine those to a level where we need to achieve the required functionality.

Stormtrooper: Shooter and TargetMisser
XWingFighter: Shooter and Flyer
Padawan: BasicForceApplier and LightsaberHandler
JediMaster: SuperForceApplier, LightsaberHandler, and Flyer

Example

Let's take the previous example and turn it into something a bit more practical.

const Flyer = function(state) {
    return {
        fly() {
            console.log(`Flying ${state.ship}`);
        }
    }
}

const LightsaberHandler = function(state) {
    return {
        handle() {
            console.log(`Turn on the ${state.lightsaberColor} lightsaber - Vrummmummmmm FVISH!`);
        }
    }
}

const SuperForceApplier = function(state) {
    return {
        use() {
            console.log(`Use ${state.skill}`);
        }
    }
}

const JediMaster = function(name, skill) {
    let state = {
        name,
        skill,
        ship: 'T-70 X-wing',
        lightsaberColor: 'Green'
    }

    return Object.assign(
        {},
        Flyer(state),
        LightsaberHandler(state),
        SuperForceApplier(state)
    );
}

let ObiWan = new JediMaster('Obi-Wan', 'Mind Trick');

ObiWan.fly();

Drawbacks

Composition has a few downsides too. It tends to add more indirection to a system. It can also be more work to assemble all the components together into the desired behavior. Anything that can be implemented via inheritance can alternatively be implemented using composition.

Therefore, inheritance should be used when the cost of indirection and construction of a composition-based approach is higher than the benefits of encapsulated responsibilities and flexibility. Also when the object already has a single responsibility, and we really do just want specializations.

Finally, composition is a more robust and flexible approach to architect reusable object-oriented software, so still, it should be the go-to solution most of the time.