Low-level designCore6 min

Composition over inheritance

Build an object from parts it holds and calls, instead of from a parent it extends. Each part varies on its own, parts can be swapped while the program runs, and two independent choices no longer multiply into a class for every combination.

Watch it evolve1 / 8
The problem

Robots with drives and grippers

Wheels or tracks, a claw or suction: one subclass for each pairing.

80
Start
Stage 1
Final

1 · The situation

Warehouse robots, one class per kind

A warehouse runs fetching robots. Each one has a drive, wheels or tracks, and a gripper, a claw or a suction cup. The first version models each kind of robot as a subclass of Robot, which supplies fetch() and leaves moveTo() and pick() to each kind:

Two of the four kindsJava
abstract class Robot {
    private final String name;

    protected Robot(String name) {
        this.name = name;
    }

    abstract String moveTo(String bay);

    abstract String pick(String item);

    String fetch(String item, String bay) {
        return name + ": " + moveTo(bay) + ", " + pick(item);
    }
}

class WheeledClawRobot extends Robot {
    WheeledClawRobot(String name) {
        super(name);
    }

    String moveTo(String bay) {
        return "rolls to " + bay;
    }

    String pick(String item) {
        return "grips the " + item;
    }
}

class TrackedSuctionRobot extends Robot {
    TrackedSuctionRobot(String name) {
        super(name);
    }

    String moveTo(String bay) {
        return "crawls to " + bay;
    }

    String pick(String item) {
        return "lifts the " + item + " by suction";
    }
}
«abstract»Robot− name: String~ moveTo(String): String~ pick(String): String~ fetch(String, String): StringWheeledClawRobot~ moveTo(String): String~ pick(String): StringWheeledSuctionRobot~ moveTo(String): String~ pick(String): StringTrackedClawRobot~ moveTo(String): String~ pick(String): StringTrackedSuctionRobot~ moveTo(String): String~ pick(String): String

Drawn from the code: four subclasses, one for each pairing of drive and gripper.

R1: rolls to A3, grips the crate
R2: crawls to B7, lifts the glass sheet by suction

2 · The change

Combinations multiply, and tools can't change

Two requests arrive. The warehouse buys legged robots for the stairs, and a magnetic gripper for steel parts. And robots should change grippers during a shift: R2 has a suction cup now, but its next job is a crate.

Both are hard with subclasses:

  • Every new option multiplies the classes. Each drive must be paired with each gripper, so three drives and three grippers need nine subclasses, and every "rolls to" and "grips the" is written once per pairing.
  • An object can't change its class. R2 is a TrackedSuctionRobot for life; giving it a claw means replacing the robot with a different object.
Subclasses, 2 × 2
4
Subclasses, 3 × 3
9
Parts, 3 + 3
6

Classes needed for the drives and grippers. Subclasses grow with the product of the options; parts grow with their sum.

3 · The fix

A robot made of a drive and a gripper

Each choice that varies becomes its own small family of classes, and a robot has one of each instead of being one combination:

A Drive interface with Wheels and Tracks, and a Gripper interface with Claw and Suction. Each piece of behavior is written once.

Java
interface Drive {
    String moveTo(String bay);
}

class Wheels implements Drive {
    public String moveTo(String bay) {
        return "rolls to " + bay;
    }
}

interface Gripper {
    String pick(String item);
}

class Claw implements Gripper {
    public String pick(String item) {
        return "grips the " + item;
    }
}
«interface»Drive+ moveTo(String): StringWheels+ moveTo(String): StringTracks+ moveTo(String): String«interface»Gripper+ pick(String): StringClaw+ pick(String): StringSuction+ pick(String): StringRobot− name: String− drive: Drive− gripper: Gripper~ fit(Gripper)~ fetch(String, String): String
R1: rolls to A3, grips the crate
R2: crawls to B7, lifts the glass sheet by suction
R2: crawls to C1, grips the crate

The same two fetches, then R2 with a claw. Legs or a magnet would be one new class each.

4 · Has-a or is-a

What each approach gives you

«interface»Drive+ moveTo(String): StringWheels+ moveTo(String): StringTracks+ moveTo(String): String«interface»Gripper+ pick(String): StringClaw+ pick(String): StringSuction+ pick(String): StringRobot− name: String− drive: Drive− gripper: Gripper~ fit(Gripper)~ fetch(String, String): String

One Robot holding a Drive and a Gripper. The practice checks read each part as a Strategy: an interface with several classes, given to the robot from outside.

Aspect

Inheritance

Composition

Relationship

Is a kind of

Has a part

Fixed when?

When the code is written

When the object is built, and changeable later

Two varying choices

One class per combination

One class per option

Coupling

To the parent's code, including its insides

Only to the part's interface

Cost

Little code to write at first

A forwarding call for each use of a part

5 · When to inherit

Composition first, inheritance on purpose

Composition is the safer default, not a ban on inheritance. Inherit when the subclass really is a kind of the parent everywhere it's used, the parent was designed to be extended, and there's one dimension of variation, not two. The page Inheritance shows what goes wrong when a subclass leans on how its parent works inside; holding a part instead depends only on what the part promises.

Composition's cost is the forwarding: if Robot needed twenty of the drive's methods, it would need twenty one-line methods passing calls along. That's a hint the robot should expose the part, or that the part's interface is too big.

6 · In the patterns

Most patterns are composition

Many design patterns on later pages are composition with a particular purpose:

  • Strategy: a part that does one job in interchangeable ways, like the drive and the gripper here.
  • Decorator: a part wrapped by another object of the same interface, which adds behavior.
  • Bridge: two hierarchies kept separate so each can grow, when the robots form a hierarchy of their own as well as their parts.

7 · In practice

A garage with a pricing part

In the parking-lot problem, a lot that subclasses itself for each pricing scheme (a match-day lot, a weekday lot) multiplies classes and can't switch schemes on match day. A lot that holds a pricing part, given to it from outside, can. The checks look for exactly that.

Check yourself

4 questions

1. With subclasses, how many robot classes do three drives and three grippers need?
2. Why can't the subclass version give R2 a claw mid-shift?
3. What does Robot.fetch() do in the composed version?
4. When is inheriting still the right choice?

Takeaways

Remember this

  • Composition builds an object from parts it holds and calls; inheritance builds it from a parent it extends.
  • Two choices that vary independently multiply subclasses but only add parts.
  • A part held in a field can be swapped while the program runs; a class can't.
  • Composition couples you to a part's interface, inheritance to the parent's insides.
  • Composition costs forwarding calls; inherit only for a true "is a kind of".