books / hfdp / ch-07SHEET 7 / 12 · REV ASIGN IN
MODULE 7 · HEAD FIRST DESIGN PATTERNS CH 8

The Hollywood Coffee Ritual (Template Method)

Pin the recipe's order in one place, leave blanks for the parts that differ, and discover that this is what every framework you use is doing to you.

The idea

Open any codebase and you will find two functions that are not identical but rhyme. Coffee and Tea both boil water, do their own thing, pour into a cup, and add their own condiments. Two of the four steps are character-for-character the same; the other two differ only in detail, not in position. Extracting the shared steps into helpers only half-fixes it, because each class still owns its own copy of the order — and the day someone adds a pre-heat step to Coffee and forgets Tea, the two routines quietly drift apart.

Template Method makes one move: put the ordering in a single base-class method that subclasses cannot override, and turn the varying steps into abstract methods they must fill in. The recipe becomes a form with blanks. Coffee and Tea shrink down to just their blanks. Adding a step is now a one-line edit in exactly one place, and it is structurally impossible for two drinks to disagree about step order.

Some blanks should be optional. A hook is a step the base class implements with a harmless default that subclasses may override but do not have to. Asking should I even run the condiments step lets the skeleton grow new behavior without breaking a single existing subclass. Abstract step means you must fill this in; hook means here is a socket, plug in only if you care.

Now notice who calls whom. Your Coffee class orchestrates nothing — the base class calls your code, at a moment of its choosing. Don't call us, we'll call you. That inversion is the load-bearing idea behind every framework you have ever used: handing a comparator to a sort routine, writing a test some runner discovers, registering an HTTP handler. Template Method is the smallest machine that teaches you what framework actually means. It is also the sibling of Strategy from module 1: Strategy swaps the whole recipe card at runtime, Template Method laminates one card and leaves blanks.

The bench — 4 exercises

EX 01

Fold the Duplicate Recipes

Collapse two copy-pasted brewing routines into one locked skeleton with pluggable steps, then prove a third drink drops in without you touching a file.

  1. Write Coffee and Tea classes with copy-pasted four-step prepare() bodies, each appending named steps to a shared step log, and pin the two step traces as your behavior lock.
  2. Create a CaffeineBeverage base class with a prepareRecipe() template method holding the order, plus abstract brew() and addCondiments().
  3. Make prepareRecipe() non-overridable in your language's idiom (Java final, Go unexported method on an embedded struct, a runtime guard or naming convention in TS/Python).
  4. Reduce Coffee and Tea to blank-fillers: no step ordering, and no mention of boilWater or pourInCup, anywhere in a subclass.
  5. Without editing any existing file, add a Chai subclass that implements only the two blanks and check its trace.
hint

If a subclass still has a method calling steps in sequence, you extracted helpers but kept two skeletons. Ask: could the two drinks ever disagree about order? If yes, it is not pinned.

hint

Try to override prepareRecipe() in a throwaway subclass on purpose — if it silently works, your lock is decorative.

DONE WHEN

· Both drinks' step traces are identical to the pre-refactor ones

· grep for the shared step names in the subclass files returns nothing

· Exactly one definition of prepareRecipe() exists in the module

· Chai produces a correct four-step trace with zero edits to existing files

EX 02

The Condiments Hook

Add an optional step the skeleton asks about, driven by a local config file, and prove that adding it broke none of the subclasses that ignore it.

  1. Add customerWantsCondiments() to the base class with a default body returning true, and have prepareRecipe() consult it before the condiments step.
  2. Override it in Coffee only, reading a two-line preferences.cfg file next to the source.
  3. Run with the config saying yes and confirm every trace is byte-identical to exercise 01.
  4. Flip the config to no, re-run, and compare Coffee against Tea and Chai.
  5. Diff your Tea and Chai files against their exercise 01 versions.
hint

Resist making the hook abstract for consistency — the moment it is abstract, every existing subclass breaks. Default-with-a-body is the whole feature.

hint

A hook must be invisible when nobody overrides it; if traces shifted at default, the skeleton changed more than it should have.

DONE WHEN

· With the hook answering yes, all traces are unchanged from exercise 01

· With the config set to no, Coffee logs three steps while Tea and Chai still log four

· Tea and Chai files are byte-identical to their exercise 01 versions

EX 03

The Other Side of the Arrow

Live as the subclass: implement a comparison contract and hand it to a small sorting framework you are forbidden to read into, then read the call trace and watch the arrow point at you.

  1. Write a roughly forty-line sortomatic merge sort in a folder you then treat as untouchable, whose comparison points call out to an interface or callback, and which logs enter-sort and call-compare markers to a shared call trace.
  2. In your own file, implement comparison for six ducks: order by weight, ties broken by name.
  3. Call the framework's single sort entry point with the flock, and nothing else.
  4. Read the call trace dump and find your comparison frames sitting between framework frames.
  5. Sort a list of strings with a different comparator through the same untouched framework.
hint

You may only implement the interface and call one entry point — if your file names any internal framework function, you are still driving.

hint

Feels like exercise 01 in a trench coat? It should — the subclass step just arrives as an object you hand in, which is exactly the thin border with Strategy.

DONE WHEN

· Sorted output matches your expected order including the two equal-weight ducks resolved by name

· Every recorded call to your comparison sits between framework markers

· Your file references nothing from the framework but the interface and sort()

· The same framework sorts strings with no changes to it

EX 04

You Are the Framework Now (stretch)

Own a skeleton instead of filling one: build a sixty-line test runner whose lifecycle is a template method, and make cleanup impossible to forget.

  1. Define a test lifecycle template method: setUp, runTest, tearDown, report, with setUp and tearDown as no-op hooks.
  2. Wrap the run so tearDown executes even when the test throws, and the failure is captured into the report rather than crashing the run.
  3. Write a discovery loop that finds and runs each test class — your code calls the test author, never the reverse.
  4. Ship three samples: one passing, one failing, and one that creates a temp file in setUp and deletes it in tearDown.
  5. Run all three and inspect both the report and the lifecycle log.
hint

The whole exercise hinges on one try/finally in the template method — that is where the skeleton owns the invariant stops being a slogan.

hint

This is roughly JUnit's real architecture; if it feels too small to be a framework, that is the lesson.

DONE WHEN

· Report shows one pass and one fail, and the run completes without crashing

· The temp file is gone after the run, including on the failing test's path

· The lifecycle log shows setUp then runTest then tearDown for every test, thrower included

Go deeper (after the bench)

Read Head First Design Patterns Ch. 8 alongside this module: the Coffee and Tea fold-in before exercise 01, and the Hollywood Principle and sorting-ducks spread before exercise 03. Then read Martin Fowler's free bliki entry "InversionOfControl" (martinfowler.com/bliki/InversionOfControl.html) — it traces exactly the arrow-flip you proved, from template methods up to full frameworks and dependency injection.