Skip to main content

Favor composition over inheritance

A car is a transport. A car has an engine. Reach for the second relationship the moment a second dimension of variation shows up.

Inheritance is the obvious way to reuse code, which is exactly why it gets overused.

Why it exists

The caveats only show up once you have hundreds of classes and no appetite for moving them:

  • A subclass cannot shrink its parent's interface. Unused abstract methods still need bodies.
  • Overrides must stay compatible with the base behavior, because client code holding a parent reference has no idea it got a child.
  • Inheritance leaks the superclass's internals into the subclass, which is encapsulation quietly going out the back door.
  • Subclasses are welded to superclasses. A change upstairs breaks things downstairs.
  • Worst of all, inheritance extends in exactly one dimension. Model two or three independent dimensions with it and the class count multiplies instead of adding.

A class explosion, and its fix

Composition is the alternative. Inheritance is an "is a" relationship - a car is a transport. Composition is "has a" - a car has an engine. Instead of baking a behavior into the type, hold an object that provides it, and swap that object whenever you like, including at runtime.

Aggregation counts too, the looser variant where the container holds a reference without owning the lifecycle: a car has a driver, but the driver can walk away and take a bus.

«abstract»Transportengine: Enginenav: Navigationdeliver(dest, cargo)THE«interface»Enginemove()EXTRACTED«interface»Navigationnavigate(dest)EXTRACTEDCardeliver(dest, cargo)SUBCLASSTruckdeliver(dest, cargo)SUBCLASSElectricEnginemove()ENGINECombustionEnginemove()ENGINEAutoPilotnavigate(dest)NAVIGATION
implementsextends
// One hierarchy asked to model three independent dimensions
// (cargo type x engine type x navigation type).
class Transport is
method deliver(destination, cargo)
 
class Car extends Transport
class Truck extends Transport
 
class ElectricCar extends Car
class CombustionCar extends Car
class ElectricTruck extends Truck
class CombustionTruck extends Truck
 
class AutopilotElectricCar extends ElectricCar
class ManualElectricCar extends ElectricCar
class AutopilotCombustionCar extends CombustionCar
class ManualCombustionCar extends CombustionCar
// ...and four more for trucks. Add hydrogen engines
// and you write six new classes to say one new thing.
// Each dimension becomes its own small hierarchy,
// held by reference instead of by ancestry.
interface Engine is
method move()
 
class ElectricEngine implements Engine is
method move() is
// draw from the battery pack
 
class CombustionEngine implements Engine is
method move() is
// burn fuel, make noise
 
interface Navigation is
method navigate(destination)
 
class AutoPilot implements Navigation is
method navigate(destination) is
// let the computer drive
 
class Transport is
field engine: Engine
field nav: Navigation
 
method deliver(destination, cargo) is
nav.navigate(destination)
engine.move()
 
// Swap behavior at runtime, no new subclass required.
car.engine = new ElectricEngine()
// A hydrogen engine is exactly one new class, forever.

If that shape looks familiar later, good: it is the Strategy pattern, arrived at from first principles rather than from a catalog. Most patterns are this move applied to a specific kind of pain.

Try it yourself: the transport model above still assumes cargo type (Car vs Truck) is the one dimension worth a subclass. If a fourth dimension - say, ride comfort tier - showed up, would you give Transport a fourth field, or has the class started doing too many jobs to hold that many collaborators directly? Keep your answer in mind for the next page.

The cost of overapplying it

None of this makes inheritance a mistake. A single, shallow, genuinely "is a" hierarchy is clearer than three interfaces and a constructor full of wiring, and composition is not free: every extracted dimension is a constructor parameter, an interface, and a wiring decision somebody has to make at startup. A Transport with one real cargo distinction (Car vs Truck) and no other variation gains nothing from being decomposed into engine and navigation interfaces it will never actually swap. Reach for composition when a second dimension of variation shows up, not before.

How it relates to its neighbours

This principle answers one specific fork in the road: when a new variant shows up, do you extend a parent class or hold a reference instead? Composing Objects is the more general version of the same instinct, and it doesn't need an inheritance tree to be in the picture at all - it applies just as much to a single god object with no subclasses anywhere near it, which is exactly what its own page is about.

Where you'll see it in the pattern catalog

Strategy is this principle with a name, as the transport example shows directly. Decorator, Bridge, and State all lean on the same "hold a reference instead of extending a class" move, each solving a differently shaped version of the class-explosion problem above.

Check yourself

Question 1 of 3

Why does "favor composition over inheritance" say favor rather than always?