THE PATTERN LAB / READ THE SITE
The site is
the textbook.
I’m reading Head First Design Patterns. The patterns in it are already running on this site: in the UFO, in the cave, in the factory. So each one gets the same five steps here. See it working, trace who owns what, read the real code, hold it next to the book, then change one thing yourself.
Chapter 1read
Strategy
Keep the thing that changes behind one shape, so you can swap how something is done without touching who does it.
1 · What you see
The UFO flies on every page, this one included. Press W, A, S or D and it takes off with weight: it drifts, brakes and banks into turns. (On a phone, drag it.) Now press “Pause motion” (Ⅱ) in the corner and fly again. Same keys, same craft, but it moves flat and stops dead. Nothing restarted: the way of flying was swapped mid-flight.
2 · Who owns what
- The flight loop (useUfoFlight)
- the craft’s position and speed, and the keys held down. Every frame it picks a way of flying.
- step() in flight.ts
- nothing. It takes the old craft and returns a new one, using whichever way of flying it was handed.
- glideVelocity and calmVelocity
- nothing either. Each is one rule for turning thrust into speed. They are the interchangeable behaviors.
- The pause button and your motion setting
- the choice. The loop only reads them, so the behavior can change between two frames.
3 · The code
src/app/ufo/flight.ts export type FlightOptions = { /** Inertia and banking. Off in calm mode (reduced motion or paused). */ readonly glide: boolean; }; // … const velocity = options.glide ? glideVelocity(craft, thrust, dt) : calmVelocity(thrust);src/app/ufo/useUfoFlight.ts const calm = reduce.matches || atmosphere?.dataset.paused === "true"; // … const options = { glide: !calm };4 · In the book
Head First This site Duckthe craft in the flight loop FlyBehaviora way of turning thrust into velocity FlyWithWings / FlyNoWayglideVelocity / calmVelocity performFly()step() asking for a velocity setFlyBehavior()the pause button, read every frame The book gives each behavior its own class behind an interface, and the duck holds one. Here the choice is a flag and a ternary, which is fine for two behaviors. It is a Strategy in shape, not yet in structure: a third way of flying would mean editing step() itself.
5 · Try it yourself
A third way to fly arrives: “hover drift”, where the craft keeps floating slowly after you let go. Before writing anything, list which functions you would change and which would stay exactly as they are. Then sketch what FlightOptions would look like if it held a behavior instead of a boolean.
Say it first: does step() change in your sketch, or only what gets passed into it?
No answer on this page, on purpose. Write yours down, then open the code and check.
Chapter 2you are here
Observer
One thing announces that something happened. Anyone who cares has signed up to hear it. The announcer never needs to know who is listening.
1 · What you see
Go down to the cave. Fly the UFO over a dancer, hold Space until the beam takes it, then press Esc to land. The dancer floats back down and starts telling the others what it saw, and a doubter answers. The UFO did not tell the cave to talk. It only announced “something came back”.
Go down to the caveThe subject
Beam the dish up with the UFO and land, or press the button.
1 listening · 0 sent
The observers
Owns speech bubbles and a queue of who talks next.
Owns its own opacity on the wall.
Owns a count of who has seen the sky.
What each one did
Nothing yet. Send a return.
2 · Who owns what
- The returned dancer (the subject)
- its own data-returned count. When it lands it announces a ufo:returned event with that count.
- The browser’s event system
- the list of who is listening. In the book the subject keeps this list itself; here the page keeps it for us.
- CaveVoices (an observer)
- the speech bubbles, a queue of who speaks next, and whether someone is speaking now.
- abduction.ts
- the beam. It never imports CaveVoices. Knowledge points one way: listeners know the announcer, not the reverse.
3 · The code
src/app/ufo/abduction.ts el.dispatchEvent( new CustomEvent(RETURNED_EVENT, { bubbles: true, detail: { times } }), );src/app/cave/CaveVoices.tsx stage.addEventListener(RETURNED_EVENT, onReturned); return () => { stage.removeEventListener(RETURNED_EVENT, onReturned); timers.forEach((timer) => window.clearTimeout(timer)); };4 · In the book
Head First This site WeatherData (the Subject)the returned element registerObserver()addEventListener(RETURNED_EVENT, …) removeObserver()removeEventListener, when the cave leaves the page notifyObservers()dispatchEvent(new CustomEvent(…)) update() on each displayonReturned in CaveVoices The announcement pushes a little (the count, in detail), but CaveVoices mostly pulls: it asks the event which dancer came back, then reads the page to find who is still a doubter. The book walks through both push and pull. Read speakNext and mark each line push or pull.
5 · Try it yourself
Imagine a counter in the corner of the page: “3 things have seen the sky.” Write down, without coding it, which file it would live in, what it would listen to, and which state it would own.
Predict before you look: how many lines of abduction.ts would have to change for the counter to work?
No answer on this page, on purpose. Write yours down, then open the code and check.
Chapters 6 and 10ahead
Command + State
Command turns a request into a thing you can name, hold and pass around. State lets the same request mean different things depending on what mode you are in.
1 · What you see
Before you fly, the arrow keys scroll this page like normal. Press W and the craft takes off; now the same arrows steer it, Space beams, M plays music, and Esc brings it home. The keys did not change. The mode did, and the mode decides what each key means. (This one needs a keyboard.)
2 · Who owns what
- The key press (KeyContext)
- the raw request: which key, which mode we are in, and what has focus.
- commandFor()
- nothing. It turns a key into a named command, or into null, meaning “leave this key to the page”.
- nextMode()
- nothing. It is the table of moves between wander, flying and landing.
- The flight loop (the invoker)
- the current mode and the set of commands being held. It carries each command out.
3 · The code
src/app/ufo/controls.ts export type Mode = "wander" | "flying" | "landing"; // … export type Command = Direction | "beam" | "land" | "music" | "drop" | "screen" | "reel" | "hide" | "cable"; // … const takeoff = TAKEOFF_KEYS[key.code]; if (takeoff) return takeoff; if (key.mode !== "flying") return null; // … export function nextMode(mode: Mode, command: Command): Mode { if (isDirection(command)) return "flying"; if (command === "land" && mode === "flying") return "landing"; return mode; }src/app/ufo/useUfoFlight.ts const next = nextMode(current, command); if (next === "landing") return land(); if (next === "flying" && current !== "flying") takeoff();4 · In the book
Head First This site RemoteControl (the invoker)the flight loop in useUfoFlight a Command with execute()a Command name like "beam" or "land" the Light it switches (the receiver)the craft and the beam GumballMachine’s current statemodeRef: wander, flying or landing one class per stateone string per mode, and nextMode() Both are the lighter cousins of the book’s versions. Commands here are names, not objects with execute() and undo(), and modes are strings in a table rather than objects that handle each key themselves. Reading chapters 6 and 10 next to this file will show you exactly what the full versions add.
5 · Try it yourself
Add, on paper, a “hover” mode: press H while flying and the craft holds still with the beam ready. Find every place in controls.ts and useUfoFlight.ts that asks which mode we are in.
Count them first. Then ask: if each mode were an object that answered keys itself, would that list get shorter?
No answer on this page, on purpose. Write yours down, then open the code and check.
Chapter 4previewed
Factory Method
The routine for handling an order stays in one place. The one step that makes the product is left open, and each kind of store fills it in its own way.
1 · What you see
In the X Factory, pick the Earth station and make a Transmission, then switch to the Alien station and make the same one. Same order, same routine, different style. Reflection only exists on Earth and Abduction only on the Alien side: each station decides what it can make.
Open the X Factory2 · Who owns what
- Station (the abstract creator)
- the order routine: make the post, then fit it to 280 characters.
- EarthStation and AlienStation
- a voice, and their own answer to createPost().
- Post (the product)
- its parts, the idea behind it, and whether it fits.
- The factory panel (StationFactory.tsx)
- which station and line you picked. It asks a station for a post and never builds one itself.
3 · The code
src/app/station/factory.ts export abstract class Station { // … orderPost(type: PostType, line: Line, pantry: Pantry): Post { return this.fit(this.createPost(type, line, pantry)); } // … protected abstract createPost(type: PostType, line: Line, pantry: Pantry): Post; // … export class AlienStation extends Station { // … protected createPost(type: PostType, line: Line, pantry: Pantry): Post { if (type === "abduction") return abduction(line); if (type in SHARED) return SHARED[type as keyof typeof SHARED](line, pantry, this.voice); throw new Error(`The Alien station doesn’t make “${type}”.`); }4 · In the book
Head First This site PizzaStoreStation orderPizza()orderPost() createPizza(), the factory methodcreatePost() NY and Chicago storesEarth and Alien stations PizzaPost This one follows the book closely: an abstract class, one abstract method, and subclasses that override it. The station page has the full side-by-side table.
5 · Try it yourself
A Moon station opens, and it makes one post only it knows how to make. On paper: which new class do you write, and which existing classes stay untouched?
Before you check: does orderPost() need to know the Moon exists?
No answer on this page, on purpose. Write yours down, then open the code and check.
Next chapter.
Decorator comes next in the book. When it shows up in the site’s own code, it gets a section here, built the same way.
Back to the station