The chapter uses a duck-simulation app (SimUDuck) to show how naïve inheritance breaks down under changing requirements, and how a few OO design principles lead naturally to the Strategy pattern.

The SimUDuck problem

SimUDuck simulates ducks swimming and quacking, with a Duck superclass and various subclasses. A new requirement arrives: ducks need to fly. The obvious move is to add fly() to Duck. But that makes every subclass fly, including ones that shouldn’t (a RubberDuck doesn’t fly, a DecoyDuck neither flies nor quacks). Overriding the unwanted behaviour in each subclass is brittle and has to be repeated for every new duck.

The lesson: inheritance shouldn’t be used purely for code reuse, because behaviour that varies across subclasses ends up scattered and duplicated.

Why an interface alone doesn’t fix it

A Flyable/Quackable interface stops rubber ducks from inappropriately flying — only the subclasses that should fly implement Flyable. But interfaces carry no implementation, so all code reuse is lost. Every flying duck reimplements fly(), and if there are several kinds of flying behaviour, the duplication multiplies. So interfaces alone trade one maintenance nightmare for another.

Design principles

The fix isn’t a single pattern but a set of principles.

Identify what varies and separate it from what stays the same. If some aspect of your code keeps changing with each new requirement, pull that behaviour out and encapsulate it, so you can alter or extend it later without affecting the code that doesn’t change. Here, fly() and quack() are what vary across ducks, so they get pulled out of Duck into their own behaviour classes.

Program to an interface, not an implementation. This really means “program to a supertype.” The point is polymorphism: declare variables as a supertype (an abstract class or interface) so the concrete runtime object isn’t locked in. You can program to an interface without literally using the language’s interface construct — what matters is that the declared type is a supertype.

Favour composition over inheritance. A duck has a fly behaviour and has a quack behaviour, rather than inheriting them. This is the key shift that makes the design flexible.

Designing the duck behaviours

Goals for the redesign:

  • Flexibility — the original problem was inflexible, hard-coded behaviour.
  • Assignable behaviour — instantiate a MallardDuck and give it a specific type of flying behaviour.
  • Changeable at runtime — provide setter methods so behaviour can be swapped while the program runs.

Each behaviour is represented by an interface — FlyBehavior, QuackBehavior — and each concrete behaviour (e.g. FlyWithWings, FlyNoWay, Quack, Squeak, MuteQuack) implements one of them. Crucially, Duck does not implement these interfaces. Separate behaviour classes do, and the Duck holds references to them. That way the duck classes don’t need to know any implementation details of their own behaviours.

A behaviour class can still have its own state and methods — e.g. a flying behaviour might track wing beats per minute, max altitude, speed.

Integrating the behaviours

The Duck class delegates instead of implementing behaviour directly:

  1. Add two instance variables, flyBehavior and quackBehavior, typed to the interfaces. Remove fly()/quack() from Duck and its subclasses.
  2. Replace them with performFly() and performQuack(), which simply delegate to the held behaviour object (e.g. flyBehavior.fly()).
  3. A concrete duck assigns the appropriate behaviour at construction — e.g. MallardDuck sets quackBehavior = new Quack() and flyBehavior = new FlyWithWings().

There’s still a wrinkle: assigning concrete behaviour types in the constructor is itself programming to a concrete implementation. Later patterns address this.

Changing behaviour at runtime

Add setters like setFlyBehavior() / setQuackBehavior() to Duck. Now a ModelDuck that starts with FlyNoWay can be handed a FlyRocketPowered behaviour at runtime and start flying. This dynamic swap is exactly what inheritance couldn’t give us.

IS-A vs HAS-A

In the inheritance-heavy version, relationships were primarily IS-A: a MallardDuck is a Duck, with all behaviour overridden in the children.

In the new design, the IS-A relationship between Duck and specific ducks remains (and still makes sense), but it now carries almost no behaviour — just display(), which each type implements differently. The fly and quack behaviours are wired in via HAS-A: every duck has a fly behaviour and has a quack behaviour, chosen at construction and swappable at runtime. This is composition, and it’s what buys the flexibility — you can encapsulate a family of algorithms and change behaviour at runtime, as long as the composed object implements the right interface.

The Strategy pattern

This refactoring is the Strategy pattern:

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.

My take

The standout idea is that “change is the one constant” — requirements shift, priorities shift, early assumptions turn out wrong, bugs surface. The principles here are really just ways of localising the blast radius of change. “Separate what varies” and “favour composition over inheritance” are the two I keep reaching for in real code.

Source

Head First Design Patterns by Eric Freeman & Elisabeth Robson — Chapter 1: Welcome to Design Patterns.