books / hfdp / ch-01SHEET 1 / 12 · REV ASIGN IN
MODULE 1 · HEAD FIRST DESIGN PATTERNS CH 1

Ducks That Swap Their Wings

Inheritance decides what a duck can do before you have met the duck; composition lets you change your mind while the program is still running.

The idea

Inheritance is a great deal until the first exception. You have a Duck base class, every duck quacks and swims, and every subclass gets that for free. Then someone adds fly() to the parent and the rubber duck takes off, because inheritance hands every child everything whether it makes sense or not. One line in one file silently changed classes its author never opened.

The obvious escapes are worse. Override fly() to do nothing in RubberDuck and correctness now depends on every future duck author remembering the ritual. Push fly() into an interface each duck implements itself and you have destroyed reuse: forty ducks means forty copies of flight code, and one tweak means forty edits.

The real move is to separate what changes from what stays. Swimming is stable, so it stays on Duck. Flying and quacking vary per duck, so each becomes its own small family of objects: FlyWithWings, FlyNoWay, Quack, Squeak, MuteQuack. The duck holds a field typed to the behavior interface, not to any one implementation, and performFly() simply delegates to whatever is plugged in.

That swap from being a flyer by ancestry to having a fly behavior is the whole payoff. A grounded model duck can get rocket-powered flight mid-simulation through one setter, with no existing class edited. You have just met Strategy: a family of interchangeable algorithms behind one interface, chosen at runtime, and a name you can now say in five words instead of a diagram.

The bench — 4 exercises

EX 01

Break It Three Ways

Reproduce the duck simulator's flying-rubber-duck bug, then try both textbook non-fixes and measure exactly how each one fails. Patterns are answers, and this makes sure you have personally met the question.

  1. Write a small Duck base class with fly() and quack() in it, plus MallardDuck and RubberDuck, and run a sim loop that prints what each duck does.
  2. In a throwaway branch, override fly() to a no-op in RubberDuck, then add a DecoyDuck that also should not fly and see what it inherits.
  3. In a second branch, delete fly() and quack() from the base and give each duck its own Flyable and Quackable implementations.
  4. Count the duplicated lines of flight code across your ducks in that second branch.
  5. Write three dated lines in NOTES.md tagged inherit, override and interfaces: what broke and why it will not scale.
hint

The no-op override is not wrong once; it is wrong forever, because it must be remembered by everyone for every new duck. DecoyDuck is the new hire nobody told.

hint

Keep the failures in branches. Your main branch should still show the bug when you start the next exercise.

DONE WHEN

· Running main prints a rubber duck flying

· The interface branch contains the same flight body in two or more files

· NOTES.md has three entries, one per failure mode

EX 02

Extract the Wings

Do the canonical Strategy refactor: pull the varying behaviors out into FlyBehavior and QuackBehavior families while the simulator's correct output stays byte-for-byte identical.

  1. Define FlyBehavior and QuackBehavior interfaces and write FlyWithWings, FlyNoWay, Quack, Squeak and MuteQuack as small classes.
  2. Give Duck two behavior fields plus performFly() and performQuack() that only delegate, and keep swim on Duck and display abstract.
  3. Wire each duck's constructor to its correct pair of behaviors.
  4. Add setFlyBehavior and setQuackBehavior now, before you need them.
  5. Print a behavior table of every duck against fly and quack and check every cell against what the duck should do.
hint

If you are unsure where display() goes, ask whether it varies independently of duck identity. It does not, so it stays abstract on Duck.

hint

Encapsulate what varies does not mean move everything out; it means cut along the seam where change actually happens.

DONE WHEN

· No fly or quack method bodies remain anywhere in Duck or its subclasses

· Five concrete behavior classes exist across the two interfaces

· The behavior table is correct for every duck and the rubber duck no longer flies

EX 03

Rocket Wings at Runtime

Add a brand-new flight capability and hot-swap it onto a duck that is already alive, without editing a single existing class. This is the flexibility the refactor bought you.

  1. Record a checksum of every source file you finished in the previous exercise (shasum over the directory, saved to a file).
  2. Add FlyRocketPowered as a new file only.
  3. In the sim script, create a ModelDuck that starts with FlyNoWay and call performFly to watch it fail.
  4. On the same live instance, call setFlyBehavior(new FlyRocketPowered()) and fly again, printing the object's identity both times.
  5. Re-run the checksums and confirm nothing but new files and the sim script changed.
hint

If the checksum of Duck itself changed, you added the setter too late. Design the seams before you need them, or extension quietly becomes modification.

hint

Print the object id or hash on both attempts so you can prove it is one duck, not two.

DONE WHEN

· The transcript shows one object id failing to fly and then rocket-flying

· Checksums of all previously finished files are unchanged

· The behavior table from the previous exercise is still fully correct

EX 04

No Ducks Allowed

Apply Strategy in a domain with no ducks in it: a text formatter whose alignment logic lives in one giant conditional. Recognizing a strategy waiting to happen is the actual skill.

  1. Write a formatParagraph() that pads lines to a width with a single if-chain over left, right, center and justify, and save its output for a fixture paragraph as golden files.
  2. Extract an AlignStrategy interface that receives already-broken lines and returns padded ones, with one class per alignment.
  3. Make formatParagraph pick a strategy and delegate, then diff its output against your golden files.
  4. Add a fifth strategy in a new file only: ragged, where no line may be more than fifteen percent shorter than the line above it.
  5. Confirm the four original outputs are untouched with the fifth strategy in place.
hint

The seam is how lines get padded, not how text gets split into words. If your interface takes raw paragraphs you cut in the wrong place.

hint

Capture the golden files before refactoring, not after, or they only prove your bug is stable.

DONE WHEN

· All four original alignments produce output byte-identical to the golden files

· formatParagraph contains no alignment conditionals

· The fifth strategy works without editing any existing formatter file

Go deeper (after the bench)

Read Head First Design Patterns chapter 1, pages 1 to 36, now. Do it after the first exercise rather than before, because the chapter's fake-outs land very differently once you have watched them fail on your own machine. If the composition argument still has not clicked, Refactoring Guru's Strategy page is free and has an unusually honest section on when not to use it.