Design Patterns, Explained Simply · Foundations: Thinking in Patterns

Program to Interfaces, Favour Composition

The two ideas behind almost every pattern: depend on what an object can do, not what class it is, and build behaviour by combining objects instead of deep inheritance trees.

The problem

A game has robots. You start with Robot, then add WalkingRobot and FlyingRobot as subclasses. Then the designers want a robot that walks and flies, one that swims, one that swims and flies, one that changes from walking to flying mid-level...

With inheritance, every combination becomes a new subclass: WalkingSwimmingRobot, FlyingSwimmingRobot, and so on. The number of classes explodes, the same movement code is copied between them, and a robot can't change how it moves at runtime because its class is fixed.

Real-world analogy

A power drill doesn't come in a "drill that screws" model and a "drill that sands" model. It has a standard chuck (the interface) and you snap in whichever bit you need (composition). Any bit that fits the chuck works, and you can swap bits mid-job.

The idea

The Gang of Four book opens with two principles. Nearly every pattern in this course applies one or both:

  1. Program to an interface, not an implementation. Code that uses a robot should depend on "something that can move()", not on the concrete class FlyingRobot. Then any object with move() works.
  2. Favour object composition over class inheritance. Instead of a robot being a flying robot (inheritance), a robot has a movement object (composition). Combinations come from plugging objects together, not from new subclasses.

Inheritance is still right for real "is-a" relationships with shared behaviour that never varies independently. The trouble starts when you use it to mix and match features.

In dynamic languages, "interface" can just mean "has the right method" (duck typing in Python and PHP, structural typing in TypeScript). In Java it is usually an explicit interface.

The code

The robot gets its movement from a separate object, so new combinations need no new robot classes, and movement can change at runtime.

Robots that compose movement instead of inheriting itTry it yourself
class Walk:
    def move(self, name):
        return f"{name} walks on two legs"


class Fly:
    def move(self, name):
        return f"{name} flies with rotors"


class Swim:
    def move(self, name):
        return f"{name} swims with a propeller"


class Robot:
    def __init__(self, name, movement):
        self.name = name
        self.movement = movement  # composition: the robot HAS a movement

    def move(self):
        return self.movement.move(self.name)


scout = Robot("Scout", Walk())
print(scout.move())
scout.movement = Fly()  # change behaviour at runtime, no new class
print(scout.move())
print(Robot("Diver", Swim()).move())

Output

Scout walks on two legs
Scout flies with rotors
Diver swims with a propeller

Three movement classes cover every robot. With inheritance, walk/fly/swim combinations would need up to seven subclasses, and none could switch at runtime.

Common mistakes

  • "Composition means never inherit." Inheritance is fine for genuine "is-a" relationships with stable shared code, like a framework's base controller.
  • Interfaces for everything. An interface with one implementation that will never have a second adds a file and no flexibility. Introduce it when a second implementation or a test double appears.
  • Deep hierarchies "for reuse". Reusing code through a base class couples every subclass to the base. A change there ripples everywhere (the fragile base class problem).

Key points

  • Program to an interface: depend on what an object can do, not on its concrete class.
  • Favour composition: give objects other objects to delegate to, instead of subclassing for every combination.
  • Composition allows runtime changes and avoids class explosion.
  • Use inheritance for true "is-a" relationships, not to mix features.
  • These two ideas power Strategy, Decorator, Bridge, State and most other patterns in this course.

Practice questions

What does "program to an interface, not an implementation" mean in practice?

Answer

Code that uses an object should depend on the operations it needs (an interface, or in dynamic languages just the method names), not on a concrete class. For example, a Robot asks for any Movement with move(), so Walk, Fly or a test double all work without changing Robot.

Why can inheritance lead to a "class explosion"?

Answer

When you use subclasses to combine independent features (walking, flying, swimming), every combination needs its own subclass, so the count grows multiplicatively. Composition gives each feature its own object, so you need one class per feature and combine them freely.

When is inheritance still the right choice?

Answer

For a real "is-a" relationship where the subclass is a specialised version of the parent and the shared behaviour does not vary independently, for example a framework's base Controller or an IOException subclass. Avoid it when you are using it only to reuse code or mix features.

How does duck typing relate to programming to an interface?

Answer

In Python, PHP (without type hints) and TypeScript (structural typing), any object with the right methods fits, even without declaring an interface. The principle is the same: depend on behaviour, not on a specific class. Java expresses it with an explicit interface.

Open in the app to mark lessons complete, take notes and track your progress.