Mediator
One object that coordinates many, so they do not all know each other.
01The intuition
The problem it solves
A form on a screen has a country dropdown, a state dropdown, a postcode box and a submit button. Choosing a country must refill the states, clear the postcode and disable submit until everything is valid. The obvious code has each widget call the others: the country widget knows about the state widget and the postcode box; the postcode box knows about the button. Four widgets already have six links between them, and adding a city field means touching all of them.
class CountryBox:
def on_change(self, country: str) -> None:
self.state_box.load(states_of(country))
self.postcode.clear()
self.submit.set_enabled(False)
class PostcodeBox:
def on_change(self, text: str) -> None:
ok = self.state_box.value and is_valid(text, self.country_box.value)
self.submit.set_enabled(ok)
# every widget holds references to every other widget
Build it one piece at a time
Step one: pick one object whose job is to know how the widgets relate. Widgets stop talking to each other and only report what happened to that one object. Programmers call it a mediator.
class FormMediator:
def __init__(self, country: "CountryBox", state: "StateBox",
postcode: "PostcodeBox", submit: "Button") -> None:
self.country, self.state, self.postcode, self.submit = country, state, postcode, submit
for w in (country, state, postcode):
w.mediator = self
def changed(self, widget: object) -> None:
if widget is self.country:
self.state.load(states_of(self.country.value))
self.postcode.clear()
self.submit.set_enabled(self.is_valid())
def is_valid(self) -> bool:
return bool(self.state.value) and is_valid(self.postcode.value, self.country.value)
Step two: each widget shrinks to its own concern plus one line that tells the mediator something changed. A widget can now be tested alone with a fake mediator, and reused in a different form.
class CountryBox:
def __init__(self) -> None:
self.value = ""
self.mediator: FormMediator | None = None
def on_change(self, country: str) -> None:
self.value = country
if self.mediator: self.mediator.changed(self)
class PostcodeBox:
def on_change(self, text: str) -> None:
self.value = text
if self.mediator: self.mediator.changed(self)
Step three: adding the city field means one new widget and one new branch in the mediator. The count of links between objects is now the number of widgets, not the number of pairs.
Why it works
The principle: many-to-many knowledge between colleagues becomes one-to-many knowledge through a single coordinator. The invariant is that no colleague holds a reference to another colleague; all cross-object rules live in the mediator, so there is exactly one place to read them. Where it breaks: the mediator becomes a thousand-line god object that does the widgets' work for them, or a colleague sneaks a direct reference to another "just this once". Interviewers probe by asking where a new rule goes (always the mediator) and how you keep the mediator from growing without bound (split by screen, or move rule evaluation into small rule objects the mediator calls).
The trade-off you must say out loud
You have traded many small couplings for one large one. For two widgets, a direct call is clearer. The mediator is also easy to confuse with an observer: with observers the subject does not know its listeners and there is no central brain; with a mediator the brain is the point. The follow-up question is usually that distinction, or whether the mediator should receive typed events rather than a bare widget reference.
Where it shows up in the 100 Google problems
- In-process pub/sub event bus: the bus is a mediator between publishers and subscribers that never meet.
- Multi-elevator dispatcher: cars do not talk to each other; the dispatcher assigns calls and resolves conflicts.
- Game matchmaking: players never see the queue of others; the matchmaker pairs them and creates the room.
- Customer-support agent dispatcher: requests and agents are colleagues; the dispatcher holds every rule about who serves whom.