Skip to Content
Course content

74: Mixins and Composition

Click on the "Edit" button in the top corner of the screen to edit your slide content.

For a long time, the "correct" way to teach object-oriented programming was to lean heavily on inheritance. You've already seen how extends works in JavaScript, and it feels intuitive at first. You have a base class, you extract the common logic, and you build specialized versions of that class. It’s a clean, logical tree. Or at least, it is for the first twenty minutes of a project.

The Inheritance Trap

Let's say we're building a game. We start with a Character class. Then we realize we need a Warrior and a Mage, so we extend Character into those two. Simple. But then the design doc changes. Now we need a Paladin—who is basically a Warrior that can also cast heals—and a BattleMage—who is a Mage that can swing a sword.

class Character {
  constructor(name) { this.name = name; }
}

class Warrior extends Character {
  attack() { console.log(`${this.name} swings a sword!`); }
}

class Mage extends Character {
  castSpell() { console.log(`${this.name} casts a fireball!`); }
}

// Now what? 
// class Paladin extends Warrior { ... } // But how do I get the Mage's healing?
// class Paladin extends Warrior, Mage { ... } // JavaScript doesn't allow this.

This is where most developers hit a wall. Since JavaScript doesn't support multiple inheritance, you're forced to either duplicate code (copying castSpell into the Paladin class) or push logic further up the chain into the Character base class. If you do the latter, you end up with a "fat" base class where every single character has every single ability, even if a basic Peasant shouldn't be able to cast a Level 9 Meteor spell. I've spent way too many hours untangling these "god objects" in legacy codebases.

Picking and Choosing with Mixins

The alternative is to stop thinking about what an object is and start thinking about what it does. This is the core of composition. Instead of a rigid hierarchy, we create small, focused pieces of functionality—mixins—that we can plug into any object regardless of its place in a class tree.

In JavaScript, a mixin is essentially just a function or an object that provides a set of methods. We can use Object.assign() to "mix" these capabilities into our class prototype or directly into an instance.

const canAttack = {
  attack() { console.log(`${this.name} swings a weapon!`); }
};

const canCastSpells = {
  castSpell() { console.log(`${this.name} casts a spell!`); }
};

const canHeal = {
  heal() { console.log(`${this.name} restores some HP!`); }
};

class Character {
  constructor(name) { this.name = name; }
}

class Paladin extends Character {}
Object.assign(Paladin.prototype, canAttack, canHeal);

class BattleMage extends Character {}
Object.assign(BattleMage.prototype, canAttack, canCastSpells);

const arthas = new Paladin("Arthas");
arthas.attack(); // Works
arthas.heal();   // Works

The Cost of Flexibility

Now, I'm not telling you that composition is a silver bullet. There are trade-offs here that you need to be aware of. The biggest issue is name collisions. If canAttack and canHeal both happened to have a method called init(), the one applied last via Object.assign would simply overwrite the first one. You lose the safety of the compiler (or the engine) telling you that you're overriding a method; it just happens silently.

You also lose some of the clarity of instanceof. While arthas instanceof Paladin still works, you can't easily check arthas instanceof canHeal because canHeal isn't a class—it's just a plain object. If your logic depends heavily on checking the "type" of an object, composition makes that harder. But in my experience, if you find yourself using instanceof constantly, you're usually fighting against the design of your system anyway.

The win here is agility. When the game designer decides that Dogs can now Heal, you don't have to restructure your entire animal kingdom hierarchy. You just plug in the canHeal mixin and move on with your day.




📋 Practical Task

Build a Modular Smart-Home Device System

You are tasked with creating a system for smart home devices. Instead of using a deep inheritance tree, use mixins and composition to build the following devices based on their capabilities.

Requirements:

  • Create three mixin objects:
    • connectable: contains a method connect() that logs "Connecting to WiFi...".
    • programmable: contains a method setSchedule(time) that logs "Schedule set for [time]".
    • batteryPowered: contains a method checkBattery() that logs "Battery at 85%".
  • Create a base class Device that takes a name in the constructor.
  • Create three specific device classes that extend Device:
    • SmartBulb: Should be connectable and programmable.
    • SmartCamera: Should be connectable and batteryPowered.
    • SmartThermostat: Should be connectable and programmable.
  • Instantiate one of each and call at least two of their mixed-in methods to verify they work.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.