The Duck Orchestra (Compound Patterns & MVC)
Five patterns you already built, wired into one program whose final quack count is only right if every seam is.
The idea
Every module so far handed you one pattern in a padded room. Real programs are not like that: the interesting problems show up where patterns meet, an adapter feeding a decorator feeding an observer. A compound pattern is not a new thing to memorize; it is several patterns you already know, each solving its own sub-problem inside one design.
So you rebuild the duck simulator, but now a goose wants in (wrong interface, so Adapter), a scientist wants every quack counted without ducks knowing (Decorator), counting only works if every duck is wrapped at birth (Factory), ducks come in flocks you command as one (Composite), and the scientist wants to be told rather than poll (Observer). The test is almost insultingly simple: run it and count the quacks. If the number is right, every layer worked.
Model-View-Controller sounds like framework religion, but it is three patterns in a trench coat. The model is an observable that holds state and announces changes to whoever listens. The view subscribes and renders, often composing nested display parts. The controller is the view's pluggable brain, interpreting input and deciding what to do to the model, swappable because it sits behind an interface.
The payoff is live pluggability. Since the model only knows how to notify observers, nothing stops you attaching a second, completely different view to the same running model, both dancing to one beat, neither aware of the other. That is why every serious UI stack is a remix of this shape: the decoupling is what makes adding another screen a feature instead of a rewrite.
The bench — 4 exercises
Wire the Orchestra
Compose five patterns you have already certified into one duck simulator, then prove they cooperate with a single number: the total quack count for a fixed scene.
- Define a Quackable interface with observer registration, and wrap your module-1 ducks in thin quackable shims rather than rewriting them.
- Write a GooseAdapter over a do-not-edit vendor goose that only knows how to honk, and a QuackCounter decorator that increments a shared count per quack.
- Write DuckFactory and CountingDuckFactory, the latter returning every duck pre-wrapped in QuackCounter.
- Write Flock, a composite whose quack() fans out to members and whose registerObserver() recurses into sub-flocks.
- Build a fixed scene (a flock of 4, a nested sub-flock of 3, one adapted goose, one solo duck), register a single Quackologist observer on the top-level flock, poke everything once, and hand-compute the expected total before you run it.
hint
If the count runs high, your composite is both forwarding and counting — a Flock should never be wrapped in QuackCounter.
hint
If the Quackologist misses nested ducks, registerObserver is not recursing into sub-flocks.
DONE WHEN
· The printed total exactly matches the count you worked out by hand, goose included
· One observer registered on the top flock logs every leaf quack, including nested ones
· Every duck quacks exactly once per pass, no duplicates in the log
· The vendor goose file is byte-identical to how it shipped
The Transparency Test
Run the same scene twice, once built by the counting factory and once by the plain one, and prove the auditing layer is invisible to everything downstream.
- Make one entry script build and run the identical fixture scene from either factory, chosen by a flag.
- Capture each run's ordered output lines to a file, plus the final count.
- Diff the two transcripts and confirm the counted run's total matches exercise 01 while the uncounted run's is zero.
- Grep the whole project for QuackCounter construction sites.
hint
If the transcripts differ, your decorator is printing as well as counting; a decorator that changes observable behavior is a bug with a pattern name.
hint
If grep finds a second construction site, you hand-wrapped a duck somewhere — exactly the leak factories exist to prevent.
DONE WHEN
· diff of the two transcripts reports no differences
· Counted run total equals the exercise 01 number; uncounted run total is 0
· grep shows QuackCounter constructed in exactly one place, inside CountingDuckFactory
MVC With Your Bare Hands
Build a terminal BPM box in three files with hard boundaries, so every arrow in the famous triangle is a line you wrote rather than framework magic.
- Write BeatModel: owns bpm and running state, emits beat and bpm_changed to registered observers, does zero I/O, and ticks from an injectable clock rather than a real timer.
- Write AsciiView: subscribes and renders a one-line beat bar plus BPM readout on each notification, never mutating the model.
- Write Controller behind a ControllerInterface: reads commands (+, -, s, q) live or from a scripted command file, and only calls model methods.
- Run a scripted session end to end and save its output as a golden transcript, then re-run to confirm it reproduces byte for byte.
- Check the imports: the model file must import neither view nor controller.
hint
Fake time. A stepped clock is what makes the transcript deterministic, and it is the same seam real apps use to make UIs testable.
hint
If the view needs to know a command exists, the boundary has slipped — it should only read state and subscribe.
DONE WHEN
· Re-running the scripted session reproduces the transcript exactly
· The model file imports neither the view nor the controller
· Every scripted mutation produced exactly one notification, checked with a temporary probe observer
Second View, Live — Then a Heart Transplant
Attach a log view to an already-running model without touching a certified file, then point your unmodified views at a foreign heart-rate model through an adapter.
- Record checksums of BeatModel, AsciiView and Controller as they stand after exercise 03, and do not edit them again.
- Write LogView, a second observer that appends every beat and bpm event to beatbox.log, and attach it halfway through a scripted session.
- Compare the log's event stream against the portion of the ascii transcript after attachment.
- Write HeartAdapter over a vendor HeartModel that only exposes getHeartRate() and has no setBPM, and run your unchanged views against it.
- Re-check the three checksums at the end.
hint
Some operations legitimately do not exist on a heart — you cannot set its BPM. Absorb that at the adapter, never by editing upstream code.
hint
If events go missing after attachment, your model is snapshotting its observer list somewhere instead of reading it live.
DONE WHEN
· Events in beatbox.log match the ascii transcript exactly for the post-attachment window, with no drift
· Events before attachment appear in the transcript but not in the log
· Both views render correct BPM against the heart's pulse fixture
· Checksums of model, view and controller are unchanged; only new files were added
Go deeper (after the bench)
Read Head First Design Patterns Ch. 12 now: the DuckSimulator half after exercise 02 and the DJ beat MVC half after exercise 03, so the chapter reads as commentary on code you already made pass. Then read Martin Fowler's free "GUI Architectures" (martinfowler.com/eaaDev/uiArchs.html) to see which parts of MVC are the pattern and which are folklore.