3 min read

Composition over Inheritance

Composition over Inheritance is a principle in object-oriented programming (OOP) that suggests that classes should achieve code reuse and polymorphic behavior by containing instances of other classes that implement the desired functionality, rather than inheriting from a base or parent class.

1. The Core Concept

  • Inheritance ("Is-A"): You design your types around what they are. (e.g., A Car is a Vehicle).
  • Composition ("Has-A"): You design your types around what they do or have. (e.g., A Car has an Engine).

The principle argues that Composition is generally more flexible and less brittle than Inheritance.

2. The Problems with Inheritance

While inheritance is powerful, overuse leads to several issues:

The Fragile Base Class Problem

Changes to the parent class can inadvertently break child classes. The child class is tightly coupled to the implementation details of the parent.

The Gorilla / Banana Problem

"You wanted a banana but what you got was a gorilla holding the banana and the entire jungle." β€” Joe Armstrong

When you inherit from a class, you get everythingβ€”all its data and methods, even the ones you don't need.

Hierarchy Explosion

Imagine a game with Monster. You have FlyingMonster and SwimmingMonster. What happens when you need a FlyingSwimmingMonster? You can't inherit from both (in many languages). You end up with a deep, messy tree.

3. The Solution: Composition

Instead of inheriting behavior, you compose it. You create small, focused classes (components) and combine them.

Example: Inheritance (The Wrong Way)

class Robot {
    void move() { ... }
    void clean() { ... }
}

class CleaningRobot extends Robot {
    // Inherits move and clean
}

class FightingRobot extends Robot {
    // Inherits move and clean... but wait, fighting robots don't clean!
    // Now you have a useless clean() method.
}

Example: Composition (The Right Way)

interface MovementBehavior {
    void move();
}

interface CleaningBehavior {
    void clean();
}

class Robot {
    private MovementBehavior mover;
    private CleaningBehavior cleaner;

    public Robot(MovementBehavior mover, CleaningBehavior cleaner) {
        this.mover = mover;
        this.cleaner = cleaner;
    }

    void performTasks() {
        if (mover != null) mover.move();
        if (cleaner != null) cleaner.clean();
    }
}

// Usage
Robot cleanerBot = new Robot(new Wheels(), new Vacuum());
Robot fighterBot = new Robot(new Legs(), null); // No cleaning behavior!

4. Benefits of Composition

  1. Flexibility: You can change behavior at runtime by swapping components (e.g., changing a Engine from Gas to Electric).
  2. Loose Coupling: Classes depend on interfaces, not concrete implementations.
  3. Testability: Easier to mock individual components.
  4. Single Responsibility: Each component does one thing well.

5. When to use Inheritance?

Inheritance isn't dead. Use it when:

  • There is a strict "Is-A" relationship.
  • You want to enforce a common interface (polymorphism) without code reuse (Interfaces/Abstract Classes).
  • The base class code is stable and unlikely to change.

6. Mixins

Mixins are a form of code reuse that sits somewhere between inheritance and composition. A Mixin is a class containing methods that can be used by other classes without needing to be the parent class of those other classes.

  • Concept: "Included" behavior. A class can "mix in" functionality from multiple sources.
  • Language Support: Supported natively in languages like Ruby, Python (via multiple inheritance), and TypeScript/JavaScript.

Example (TypeScript)

// A Mixin that adds flying capability
function Flyable<TBase extends new (...args: any[]) => {}>(Base: TBase) {
    return class extends Base {
        fly() {
            console.log("Flying high!");
        }
    };
}

// A Mixin that adds swimming capability
function Swimmable<TBase extends new (...args: any[]) => {}>(Base: TBase) {
    return class extends Base {
        swim() {
            console.log("Swimming deep!");
        }
    };
}

class Monster {}

// Compose a new class using Mixins
const FlyingSwimmingMonster = Swimmable(Flyable(Monster));

const monster = new FlyingSwimmingMonster();
monster.fly();
monster.swim();

programming/object-oriented-programming programming/design-patterns programming/software-design-principles