books / hfdp / ch-04SHEET 4 / 12 · REV ASIGN IN
MODULE 4 · HEAD FIRST DESIGN PATTERNS CH 4–5

The Pizza Factory Floor (Factory Method, Abstract Factory & Singleton)

Take `new` off the menu: let stores decide what gets built, families stay matched, and the one shared resource stays exactly one.

The idea

Every time you write `new ChicagoStyleClamPizza()`, you have hard-wired an implementation into the caller. One of those is fine. The problem is what it grows into: an ordering function that is a page-long if/else over pizza types, copy-pasted everywhere a pizza gets made. Add a seasonal pizza and you are editing every copy.

The first move is barely a pattern. Take the creation block and give it a room of its own, a small object whose only job is making pizzas. Nothing clever happened, but creation knowledge now lives in one place, and the ordering code goes back to using a pizza instead of building one. Most real codebases never need more factory than this.

Then the chain franchises. NY wants thin crust, Chicago wants deep dish, but corporate insists every store prepares pizza the same certified way. Factory Method is that deal in code: the base store owns the algorithm and calls one step the subclass fills in. Abstract Factory goes one level up, handing a store a whole matched family of ingredients so a NY pizza can never quietly pick up Chicago's sauce. The shorthand: Factory Method is one product decided by inheritance, Abstract Factory is a family of products delivered by composition.

Singleton is the other half of this module and the loaded gun. Some resources genuinely must be single, like one supply ledger for the chain. A private constructor plus a lazy accessor enforces it, until two threads arrive at the same instant, both see nothing built yet, and you get two ledgers. You will watch that race happen on your own machine, fix it, and then discover the honest version of the requirement was usually exactly one instance passed to whoever needs it, not one reachable from anywhere.

The bench — 4 exercises

EX 01

From the Wall of new to Franchise Stores

Collapse a duplicated region-by-type conditional into a simple factory, then dissolve that factory into regional stores. You feel why Factory Method is different from a helper function.

  1. Write a small scripted order book that places one order of every type in both regions and records the concrete class, prep-step order, and receipt text. This is your behavior lock.
  2. Extract every creation branch into one SimplePizzaFactory and point all three call sites at it, so the giant conditional exists in exactly one file.
  3. Introduce an abstract PizzaStore with a fixed orderPizza skeleton (prepare, bake, cut, box) and an abstract createPizza step, then add NYPizzaStore and ChicagoPizzaStore.
  4. Open a third region by adding a CaliforniaPizzaStore and its pizzas without editing any existing store or pizza file, and check with git diff that only new files appear.
hint

If a `region` parameter survives anywhere, you built a simple factory with extra steps. Region is not data, it is which subclass you are.

hint

Grep your own source for direct pizza instantiations outside the stores directory; the count should be zero.

DONE WHEN

· Order book replays identically before and after the refactor

· No concrete pizza type is instantiated outside the stores directory

· The new region lands as added files only, with zero edits to existing ones

EX 02

The Ingredient Family Contract

Replace a la carte string lookups for ingredients with regional ingredient factories, so a pizza physically cannot be assembled from two regions.

  1. Find and note the planted cross-region bug where a NY pizza fetches Chicago's sauce; write a failing test for it before touching anything.
  2. Define a PizzaIngredientFactory interface (dough, sauce, cheese, clams) with NY and Chicago implementations, and delete the string-lookup registry.
  3. Have each store hand its ingredient factory to the pizzas it builds, so a pizza asks factory.createDough() and never names a concrete ingredient.
  4. Tag each ingredient with the factory that made it (a field only your test reads) and assert every pizza's ingredients share one origin.
  5. Add MascarponeCheese to the Chicago family and confirm only factory and ingredient files changed.
hint

If a pizza class still mentions ThinCrustDough, the family contract leaks. Regional knowledge belongs in exactly one class per region.

hint

Product code must never branch on the origin tag; it exists purely so your test can prove provenance.

DONE WHEN

· The cross-region sauce bug test now passes

· Every pizza in both regions reports a single ingredient origin

· Adding a family member touches no pizza or store file

EX 03

One Boiler, Many Threads

Build a naive lazy Singleton for a shared supply ledger, deliberately break it with concurrency, fix it idiomatically, then outgrow it by injecting the instance instead.

  1. Turn SupplyLedger into a lazy singleton with a private constructor and an unsynchronized check-then-act getInstance, keeping its fill/drain invariants.
  2. Insert a yield hook between the null check and the assignment, launch many concurrent workers at getInstance, and print the count of distinct identities you observe.
  3. Keep running until you actually see two or more identities; record that red run before fixing anything.
  4. Fix it the language-idiomatic way (eager init, holder class, sync.Once, module-level constant, or a locked double-check) and rerun the storm ten times.
  5. Write a test that constructs the store graph with an injected ledger and runs two isolated ledgers side by side in one process with no cross-talk.
hint

The natural race window is nanoseconds wide; the injected yield hook is what holds the door open long enough to see it.

hint

On single-threaded runtimes model it as async interleaving with an await at the hook, which shows the logic of check-then-act rather than true parallelism.

hint

The skeptic test is the point: exactly one exists and anyone can grab it from anywhere were never the same requirement.

DONE WHEN

· You observed two or more ledger identities in the broken version

· Ten consecutive storms report exactly one identity and zero invariant violations

· Two independent ledgers coexist in one process because stores accept a ledger instead of calling getInstance

EX 04

The Storage-Driver Registry

Optional stretch: build the skeleton real libraries use, an abstract factory of storage drivers selected by config, so identical client code runs on two backends.

  1. Take the order-archive tool that is hard-wired to a JSON file backend and pin its archive-and-reread output as a fixture.
  2. Define a StorageDriver abstract factory (connection, writer, reader) and implement json and sqlite families using only the standard library.
  3. Have each driver register itself with a registry on import or init, so the registry file names no concrete driver.
  4. Run the same scenario against both backends by flipping only a config string, and diff the outputs.
  5. From a test, register a third in-memory driver and run the same scenario unchanged.
hint

If the registry imports the sqlite driver and lists it in a dict literal, you centralized rather than opened. Flip the dependency.

hint

Self-registration idioms differ: a static block, an import side effect, or a blank import.

DONE WHEN

· Both backends produce byte-identical archive output from one entrypoint

· No concrete driver type is named outside its own driver directory

· A driver registered from a test runs the scenario without touching the registry

Go deeper (after the bench)

Read HFDP Ch. 4 (pp. 109-168) through the Factory Method definition before exercise 1, the Abstract Factory half between exercises 1 and 2, and the short Ch. 5 (pp. 169-186) whole before exercise 3, rereading its threading pages right after your red race run. Then read Misko Hevery's free post "Singletons are Pathological Liars" on the Google Testing Blog, which is the case behind exercise 3's injected-ledger step.