A booking form. Choosing a date filters the times. Choosing a time enables the guest count. Weekends require at least two guests. Submit stays disabled until all three are valid, and a warning appears when the party is too large for the slot.
What people write first
Each field talks to the fields it affects.
dateInput.onChange = () => { timeSelect.setOptions(slotsFor(dateInput.value)); guestsInput.setMin(isWeekend(dateInput.value) ? 2 : 1); submitButton.disabled = !isComplete(); warning.update();};Every field ends up knowing about every other. Five fields is up to ten relationships, and it is — a sixth field makes fifteen.
10 connections, and it is n(n−1)/2: a sixth field makes 15. Each field must know about every other, so none of them can be reused or tested alone.
Worse than the count: no field can be tested, reused or moved on its own, because each one references the others by name.
The pattern
Fields report changes to one object. That object knows the rules.
class BookingForm { constructor( private readonly date: DateField, private readonly time: TimeField, private readonly guests: NumberField, private readonly submit: Button, ) { for (const field of [date, time, guests]) field.onChange = () => this.sync(); }
/** Every rule about how these fields relate, in one readable place. */ private sync() { this.time.setOptions(slotsFor(this.date.value)); this.guests.setMin(isWeekend(this.date.value) ? 2 : 1); this.submit.disabled = !(this.date.value && this.time.value && this.guests.valid); }}Five connections instead of ten, and the fields are now generic — a DateField
that names no other field can be used in any form.
Mediator or Observer?
They solve overlapping problems and the difference is directionality.
- Mediator is a hub that knows the participants and coordinates them. The logic is centralised and explicit; you can read the rules in one file.
- Observer is a broadcast: the subject knows nothing about who is listening. The logic is distributed and implicit; you cannot read the rules anywhere, because they are wherever somebody subscribed.
Choose by whether the coordination rules are worth writing down together. A form with cross-field validation: yes, mediator. A logging system: no, observer.
Where it already is
Air traffic control is the textbook example and a genuinely good one — aircraft
do not negotiate with each other. In code: a Redux store, an XState machine
coordinating services, a ViewModel in MVVM, and a chat server relaying between
clients that never learn each other’s addresses.