Rule 12 of 38 · Chapter II — Design for Change
Prefer composition over inheritance
Why this rule exists
Deep inheritance hierarchies often create hidden dependencies and make ordinary changes unexpectedly expensive. A change to a base class ripples into every subclass, including ones you have forgotten about. Composition keeps each capability in a part you can reason about, test, and replace on its own.
In practice
Use small interfaces and composable services when behaviours need to vary independently. Assemble objects from the capabilities they need rather than inheriting a bundle of behaviours they mostly don't. When you notice a subclass overriding most of its parent, that is the hierarchy telling you to compose instead.
Example
class Bird {
fly() { /* ... */ }
}
class Penguin extends Bird {
fly() { throw new Error("can't fly"); } // broken substitution
}const walks = () => ({ move() { /* ... */ } });
const swims = () => ({ move() { /* ... */ } });
function penguin() {
return { ...walks(), ...swims() }; // compose only what fits
}When it doesn't apply
Inheritance remains useful when the relationship is genuinely stable and substitutability is clearly maintained — a true "is-a" that will not drift. Framework extension points and small, sealed hierarchies are reasonable. The rule is a default, not a prohibition.
Related rules in this book
Sources
- Design Patterns: Elements of Reusable Object-Oriented Software — Gamma, Helm, Johnson & Vlissides. Addison-Wesley, 1994 — 1.6, "Inheritance versus Composition."
- Effective Java, 3rd ed. — Bloch, J. Item 18: "Favor composition over inheritance." Addison-Wesley, 2018.