The idea: is-a or has-a

There are two common ways to reuse code between classes. With inheritance a class is a kind of its parent: Duck extends Bird and gets the parent's methods for free. With composition a class has other objects and hands work to them: a Bird holds a FlyBehavior and calls it. The page builds the same small bird game both ways, makes the same changes to both, and counts what each change costs.

Left, inheritance: Bird with fly() at the top, Duck and Sparrow below it with arrows up to Bird; duck.fly() walks up from Duck to Bird. Right, composition: one Bird class with two fields, fly and sound, pointing to a FlyWithWings object and a Quack object; duck.fly() is one call from Bird to the FlyWithWings it holds
Inheritance finds fly() by walking up the class tree; composition calls the part the object holds.

How to read the page

  • The left lane is the inheritance design, the right lane the composition design. Each shows its class diagram, the flock built in main(), and a scoreboard.
  • Add Goose, Add Penguin, Add Robots and Break Duck's Wing make that change in both designs at once; one Step moves each lane by one edit. Fly All runs b.fly() for every bird, two steps per bird.
  • Header colours: teal = a new class, orange = a class that already worked and had to be edited, yellow = the code running now, red = an error.
  • The scoreboard counts classes, edits to working classes (each time a class that was already written is changed) and copied methods.

Inheritance: a subclass is a parent

At the start Bird defines fly() ("flap wings") and an abstract sound(). Duck and Sparrow only override sound(). A call like duck.fly() is found by method lookup: start at the object's class, Duck, and walk up until a class defines fly().

When the new class really is a kind of the parent, this is the shortest code there is. A goose flaps and honks: one class, Goose extends Bird, with one method. Composition needs a new Honk part and a line of wiring, and it started with twice as many classes. (Demo: add a Goose)

When a subclass can't keep the promise

A penguin is a bird, so Penguin extends Bird looks right. But Bird promises fly(), and a penguin can't fly. The usual quick fix overrides fly() to throw. It compiles, and then any code written for Bird, like the loop in main(), crashes when it meets the penguin. That breaks the Liskov substitution principle: code that works with a parent must keep working with every subclass. See SOLID: Liskov Substitution for the square and rectangle version.

Before: Bird with fly(), and Duck, Sparrow and Penguin under it; Penguin overrides fly() to throw, and the loop over every Bird crashes with a red cross at the penguin. After: Bird with only sound(); a new FlyingBird with fly() sits under Bird, Duck and Sparrow now extend FlyingBird and are marked edited, Penguin extends Bird directly
The honest fix for the penguin reshapes the tree: a new FlyingBird layer, and every flying class is edited to extend it.

The real fix is a new layer: FlyingBird takes fly(), Bird keeps only what every bird does, and Duck, Sparrow (and Goose) are edited to extend FlyingBird. Code that flies birds must now ask for a List<FlyingBird>. That is 3 or 4 classes that already worked, opened and retested. The composition lane adds NoFly and Squawk and touches nothing. (Demo: add a Penguin)

Class explosion

Now every species gets a robot version that flies by rocket. Inheritance has one place to put behaviour: the class tree. So each robot is a subclass of its species (RobotDuck extends Duck keeps the quack) and each one needs the same rocket fly(). Java gives a class only one parent, so the rocket code can't be shared through the tree; it is copied. With 3 species that is 3 new classes and 2 copies; composition writes one RocketFly class and wires it. (Demo: robot birds)

A grid with species Duck, Sparrow, Goose, Penguin as rows and kinds normal and robot as columns. Inheritance needs a class in every cell, 8 classes, with the rocket fly() copied in 3 of them. Composition needs one class per fly behaviour (FlyWithWings, NoFly, RocketFly) and one per sound (Quack, Tweet, Honk, Squawk), 7 parts, and each bird is a wiring of one of each
Two independent choices multiply as subclasses (species × kind) but only add as parts (fly behaviours + sounds).

The general rule: when behaviour varies along two independent axes, here how a bird flies and how it sounds, subclasses grow like the product of the choices and parts grow like the sum. With a third axis (say, how a bird swims) the product gets out of hand fast.

Composition: has-a, and delegation

In the composition design there is one Bird class. It holds two fields typed by interfaces, FlyBehavior fly and SoundBehavior sound, and its fly() just calls fly.fly(). That forwarding call is delegation. A kind of bird is not a class any more but a choice of parts: new Bird(new FlyWithWings(), new Quack()). Each behaviour is a small class behind an interface, which is the Strategy pattern.

The price is visible on the scoreboard at the start: 6 classes against 3, and every call takes one more hop. Wrapping an object in another that has the same interface, as in the Decorator and Proxy patterns, is composition too.

Changing behaviour while the program runs

An object's class is fixed when it is created. If the duck breaks a wing during the game, the inheritance design can only give Duck a hurt flag and its own fly() that checks it: an edit to a working class, and every duck, robot ducks too, now carries that flag. The composition design calls duck.setFly(new NoFly()): the same object, a new part, no class edited. (Demo: a duck breaks its wing)

What the demos show

DemoInheritance: classes, edits, copiesComposition: classes, edits, copiesCheaper
Add a Goose4, 0, 07, 0, 0inheritance
Add a Penguin5, 3, 0, and one crash before the fix8, 0, 0composition
Robot birds (after a Goose)7 (+3), 0, 28 (+1), 0, 0composition
A duck breaks its wing3, 1, 07, 0, 0composition
All four changes10, 5, 310, 0, 0composition

In the last demo both designs end with 10 classes, but inheritance got there by editing working classes 5 times and copying the rocket code 3 times, and its robot penguin still can't join the List<FlyingBird>.

When to pick which

SituationPickWhy
The child really is a parent, everywhere the parent is usedinheritanceleast code; the parent's methods come for free
A framework hands you a base class to extend (a servlet, a test case, an Android activity)inheritancethat is how the framework calls your code
Some subclasses can't do what the parent promisescompositionno method has to throw; Liskov holds
Behaviour varies along two or more independent axescompositionparts add up, subclasses multiply
Behaviour must change while the program runscompositionan object can swap a field, not its class
The parent class belongs to someone else and may changecompositiona subclass depends on the parent's insides (the fragile base class problem)

The Design Patterns book (1994) put it as "favor object composition over class inheritance". Favor is not never: inheritance is still the right tool for a true, stable is-a.

Common mistakes

  • Modelling the real world instead of the behaviour. "A penguin is a bird" is true in biology; in code, is-a means "can be used everywhere a Bird is used".
  • Overriding a method to throw or do nothing. It hides the problem until some caller relies on the promise.
  • Inheriting only to reuse a helper method. If the child isn't a parent, hold a helper object instead.
  • Composition everywhere. A class made of five one-line parts with forwarding methods is harder to read than a small, honest hierarchy.

What the page leaves out

The page uses Java's single class inheritance. It leaves out interfaces with default methods, multiple inheritance and the diamond problem (C++, Python), mixins and traits (Scala, Rust, PHP), and languages built around composition such as Go's struct embedding. It also leaves out the fragile base class problem in detail, abstract classes that are meant to be extended, dependency injection frameworks that do the wiring, and the cost of the extra delegation call, which a JIT compiler usually inlines away.