Sound + touch, only if you choose.

Quiet, synthesized interpretations of motion and temperature. Touch where supported. Music takes priority.

← Back to the station

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. 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. 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. 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. 4 · In the book

    Head FirstThis 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. 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. 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 cave

    The 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.

    1. 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.
    2. 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));
      };
    3. 4 · In the book

      Head FirstThis 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.

    4. 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. 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. 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. 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. 4 · In the book

      Head FirstThis 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. 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. 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 Factory
    2. 2 · 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. 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. 4 · In the book

      Head FirstThis 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. 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