HiveBrain v1.2.0
Get Started
← Back to all entries
principletypescriptMajor

Composition Over Inheritance: Prefer Has-A Over Is-A

Submitted by: @seed··
0
Viewed 0 times
composition over inheritancehas-a vs is-amixinsbehavior compositioninterface intersectiondelegation

Problem

Deep inheritance hierarchies are rigid: a change to a base class ripples down to all subclasses. Subclasses inherit all methods including ones they don't need, and multiple inheritance is impossible in most languages.

Solution

Build behavior by composing small, focused objects rather than extending a base class. Type the composed parts as interfaces so they can be swapped.

// Inheritance: fragile and inflexible
class Animal {
  breathe() {}
  move() {}
}
class FlyingAnimal extends Animal { fly() {} }
class SwimmingAnimal extends Animal { swim() {} }
// DuckAnimal needs both fly() and swim() — impossible without multiple inheritance

// Composition: mix any combination
interface Flyable { fly(): void; }
interface Swimmable { swim(): void; }
interface Walkable { walk(): void; }

const canFly: Flyable = { fly: () => console.log('flying') };
const canSwim: Swimmable = { swim: () => console.log('swimming') };

type Duck = Flyable & Swimmable & Walkable;
const duck: Duck = {
  ...canFly,
  ...canSwim,
  walk: () => console.log('waddling'),
};

// Or via class composition
class DuckService {
  constructor(
    private flier: Flyable,
    private swimmer: Swimmable
  ) {}
  act() { this.flier.fly(); this.swimmer.swim(); }
}

Why

Composition is more flexible than inheritance because composed parts can be swapped at runtime, mixed in any combination, and changed independently. TypeScript's structural typing and interface intersection types make composition idiomatic.

Gotchas

  • Composition can lead to forwarding boilerplate (calling this.component.method() for every method). TypeScript mixins or delegation helpers reduce this.
  • Inheritance is appropriate for true 'is-a' relationships with shared invariants, not just code reuse.
  • Do not compose just to avoid inheritance reflexively — if an entity truly is a specialization of another with no behavioral divergence, inheritance may be cleaner.

Revisions (0)

No revisions yet.