Concepts / Behavioral patterns / Mediator

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

02Used in these problems