Mediator
A mediator takes the coordination between a group of objects and puts it in one place, so the objects report to it instead of calling each other. This page builds a station controller for Pedal's docks, display, kiosk and operations alerts, and counts the links it removes.
1. The pain
A Pedal station is a row of docks plus a display showing free bikes and docks, a kiosk screen that talks to riders, and an alert line to the operations team. When a dock jams, the rider must be sent to another free dock, the display must change, and if the station is now full, operations needs a van.
In the first version each dock did all of that itself: it asked its neighbours which were free, told the kiosk, updated the display and messaged operations. Every dock knew every other part. When the kiosk's interface changed, every dock class changed; when the redirect rule changed ("send riders to the nearest free dock, not the next one"), the logic was spread across all the docks.
2. The idea in one picture
Mediator puts the coordination between a group of objects (the colleagues) into one object that they all talk to instead of to each other. Docks just report what happened to them: controller.notify(self, "jammed"). The StationController decides who else needs to know and what to tell them: the kiosk redirects the rider, the display updates, operations is alerted when the station fills.
Links drop from "everyone to everyone" to "everyone to the hub". The price is that the hub holds all the coordination rules, so it must be watched so that it doesn't become a god object.
3. The roles
| Role | Its job | In Pedal |
|---|---|---|
| Mediator | Knows the colleagues and the rules for how they interact. | StationController.notify(sender, event) |
| Colleague | Reports its own events to the mediator; never calls other colleagues. | Dock |
| Other colleagues | Receive instructions from the mediator. | Display, Kiosk, OpsAlerts |
4. Before and after
A dock jam both ways (Python sketch; the full program is in section 5).
Without the pattern
class Dock:
def jam(self):
self.jammed = True
nxt = next(d for d in self.neighbours
if d.is_free())
self.kiosk.say(f"use {nxt.dock_id}")
self.display.show(self.count_bikes(),
self.count_free())
if not any(d.is_free()
for d in self.neighbours):
self.ops.send("station full")With it
class Dock:
def jam(self):
self.jammed = True
self.controller.notify(self, "jammed")
class StationController:
def notify(self, sender, event):
# the only place that knows the rules
# and the other parts
...| The next change | Without the pattern | With it |
|---|---|---|
| Redirect riders to the nearest free dock | Edit the jam logic in every dock | Edit StationController.notify |
| The kiosk's interface changes | Every dock class changes | The controller changes |
| Test a dock | Set up neighbours, a kiosk, a display and ops | A fake controller that records notifications |
The colleagues become simple and reusable; the rules about how they work together live in one place.
5. The code
Docks only call controller.notify(). The controller redirects riders when D2 jams, keeps the display right, and alerts operations when D4 fills the station.
from __future__ import annotations
# ---- Colleagues: each talks only to the controller, never to each other. ----
class Dock:
def __init__(self, dock_id: str, occupied: bool, controller: StationController) -> None:
self.dock_id, self.occupied, self.jammed = dock_id, occupied, False
self.controller = controller
def bike_returned(self) -> str:
self.occupied = True
return self.controller.notify(self, "returned")
def jam(self) -> str:
self.jammed = True
return self.controller.notify(self, "jammed")
class Display:
def show(self, bikes: int, free: int) -> str:
return f"display: {bikes} bikes, {free} free dock{'s' if free != 1 else ''}"
class Kiosk:
def say(self, text: str) -> str:
return f"kiosk: {text}"
class OpsAlerts:
def send(self, text: str) -> str:
return f"ops: {text}"
# ---- The mediator: the only object that knows how the others fit together. ----
class StationController:
def __init__(self, name: str) -> None:
self.name = name
self.docks: list[Dock] = []
self.display, self.kiosk, self.ops = Display(), Kiosk(), OpsAlerts()
def add_docks(self, occupied: list[bool]) -> None:
self.docks = [Dock(f"D{i + 1}", o, self) for i, o in enumerate(occupied)]
def notify(self, sender: Dock, event: str) -> str:
bikes = sum(d.occupied for d in self.docks)
free = [d for d in self.docks if not d.occupied and not d.jammed]
out = [f"{sender.dock_id} {'returned a bike' if event == 'returned' else 'jammed'} ->"]
if event == "jammed":
out.append(self.kiosk.say(f"{sender.dock_id} out of order, use {free[0].dock_id}" if free else f"{sender.dock_id} out of order, station full") + " |")
out.append(self.display.show(bikes, len(free)))
if not free:
out.append("| " + self.ops.send(f"{self.name} full, send a van"))
return " ".join(out)
if __name__ == "__main__":
station = StationController("S-31")
station.add_docks([False, False, False, False, True, True, True, True])
d = {dock.dock_id: dock for dock in station.docks}
print(d["D1"].bike_returned())
print(d["D2"].jam())
print(d["D3"].bike_returned())
print(d["D4"].bike_returned())
// ---- Colleagues: each talks only to the controller, never to each other. ----
class Dock {
jammed = false;
constructor(readonly dockId: string, public occupied: boolean, private readonly controller: StationController) {}
bikeReturned(): string { this.occupied = true; return this.controller.notify(this, "returned"); }
jam(): string { this.jammed = true; return this.controller.notify(this, "jammed"); }
}
class Display { show(bikes: number, free: number) { return `display: ${bikes} bikes, ${free} free dock${free !== 1 ? "s" : ""}`; } }
class Kiosk { say(text: string) { return `kiosk: ${text}`; } }
class OpsAlerts { send(text: string) { return `ops: ${text}`; } }
// ---- The mediator: the only object that knows how the others fit together. ----
class StationController {
docks: Dock[] = [];
private readonly display = new Display();
private readonly kiosk = new Kiosk();
private readonly ops = new OpsAlerts();
constructor(private readonly name: string) {}
addDocks(occupied: boolean[]): void { this.docks = occupied.map((o, i) => new Dock(`D${i + 1}`, o, this)); }
notify(sender: Dock, event: "returned" | "jammed"): string {
const bikes = this.docks.filter((d) => d.occupied).length;
const free = this.docks.filter((d) => !d.occupied && !d.jammed);
const out = [`${sender.dockId} ${event === "returned" ? "returned a bike" : "jammed"} ->`];
if (event === "jammed") out.push(this.kiosk.say(free.length ? `${sender.dockId} out of order, use ${free[0].dockId}` : `${sender.dockId} out of order, station full`) + " |");
out.push(this.display.show(bikes, free.length));
if (!free.length) out.push("| " + this.ops.send(`${this.name} full, send a van`));
return out.join(" ");
}
}
const station = new StationController("S-31");
station.addDocks([false, false, false, false, true, true, true, true]);
const d = Object.fromEntries(station.docks.map((x) => [x.dockId, x]));
console.log(d.D1.bikeReturned());
console.log(d.D2.jam());
console.log(d.D3.bikeReturned());
console.log(d.D4.bikeReturned());import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
// ---- Colleagues: each talks only to the controller, never to each other. ----
static class Dock {
final String dockId;
boolean occupied, jammed;
private final StationController controller;
Dock(String dockId, boolean occupied, StationController controller) { this.dockId = dockId; this.occupied = occupied; this.controller = controller; }
String bikeReturned() { occupied = true; return controller.notify(this, "returned"); }
String jam() { jammed = true; return controller.notify(this, "jammed"); }
}
static class Display { String show(int bikes, int free) { return "display: " + bikes + " bikes, " + free + " free dock" + (free != 1 ? "s" : ""); } }
static class Kiosk { String say(String text) { return "kiosk: " + text; } }
static class OpsAlerts { String send(String text) { return "ops: " + text; } }
// ---- The mediator: the only object that knows how the others fit together. ----
static class StationController {
private final String name;
final List<Dock> docks = new ArrayList<>();
private final Display display = new Display();
private final Kiosk kiosk = new Kiosk();
private final OpsAlerts ops = new OpsAlerts();
StationController(String name) { this.name = name; }
void addDocks(boolean[] occupied) { for (int i = 0; i < occupied.length; i++) docks.add(new Dock("D" + (i + 1), occupied[i], this)); }
String notify(Dock sender, String event) {
int bikes = (int) docks.stream().filter(x -> x.occupied).count();
List<Dock> free = docks.stream().filter(x -> !x.occupied && !x.jammed).toList();
List<String> out = new ArrayList<>();
out.add(sender.dockId + " " + (event.equals("returned") ? "returned a bike" : "jammed") + " ->");
if (event.equals("jammed"))
out.add(kiosk.say(free.isEmpty() ? sender.dockId + " out of order, station full" : sender.dockId + " out of order, use " + free.get(0).dockId) + " |");
out.add(display.show(bikes, free.size()));
if (free.isEmpty()) out.add("| " + ops.send(name + " full, send a van"));
return String.join(" ", out);
}
}
public static void main(String[] args) {
StationController station = new StationController("S-31");
station.addDocks(new boolean[] {false, false, false, false, true, true, true, true});
Map<String, Dock> d = new HashMap<>();
for (Dock x : station.docks) d.put(x.dockId, x);
System.out.println(d.get("D1").bikeReturned());
System.out.println(d.get("D2").jam());
System.out.println(d.get("D3").bikeReturned());
System.out.println(d.get("D4").bikeReturned());
}
}Output (the same in all three languages)
D1 returned a bike -> display: 5 bikes, 3 free docks D2 jammed -> kiosk: D2 out of order, use D3 | display: 5 bikes, 2 free docks D3 returned a bike -> display: 6 bikes, 1 free dock D4 returned a bike -> display: 7 bikes, 0 free docks | ops: S-31 full, send a van
6. Try it
Count the connections
Each dock may need to talk to the display, the kiosk, operations, and other docks. Grow the station.
Without a mediator, the worst case is that any part may need to reach any other part.
7. When not to use it
What you get
- Fewer connections: n links to a hub instead of up to n(n−1)/2.
- Simple colleagues: a dock only reports its own events.
- Rules in one place: redirects and alerts are easy to find and change.
- Reusable parts: docks don't know which station they're in.
What you pay
- A hub that grows: every new rule lands in the mediator.
- A single point of failure in the design: a bug in the controller affects the whole station.
- Indirection: a dock's effect is found by reading the controller.
- Overkill for two parts: two objects can just talk.
| If… | Use instead |
|---|---|
| Others only need to be told, with no coordination | 18 Observer |
| Callers outside need a simpler interface to a subsystem | 08 Facade |
| The coordination spans services and must be durable | An orchestrator or saga (System design track) |
| Only two or three objects interact in fixed ways | Direct calls |
8. How it goes wrong
9. Remember it and say it
How this hook works: what “Talk to the tower” means
What the slogan means, phrase by phrase
- “Talk”
- Colleagues report events.
- “to the tower”
- The mediator:
StationController. - “tells the others”
- The mediator decides who needs to know.
Why this picture fits: Air traffic control is coordination you can't do pairwise: too many planes, too many rules.
The story → the concept
| The airport | The code |
|---|---|
| The control tower | StationController |
| Planes | Docks |
| Ground crew and gates | Display, Kiosk, OpsAlerts |
| A new runway rule | "Nearest free dock" redirect |
Watch it on an example
- D2 jams and reports it to the controller.
- The controller finds D3 free, tells the kiosk to redirect, and updates the display.
- When D4 fills the last dock, the controller alerts operations.
The trap it saves you from
Every part knowing and calling every other part.
It still works when…
Yes: chat rooms, form dialogs where fields enable each other, and air traffic control itself.
In an interview or code review
- Count the links: "Docks, display, kiosk and ops would all talk to each other; a
StationControllermediator cuts it to one link each." - Say what colleagues do: they report their own events and nothing else.
- Name the risk: the mediator can become a god object; keep it to coordination rules.
- Contrast: observer broadcasts to independent listeners; mediator coordinates parts that must act together.
10. Check yourself
Who do colleagues talk to in a mediator design?
D2 jams. Who decides to send riders to D3?
The kiosk's interface changes. What changes?
What's the main risk of a mediator?
Mediator vs facade?
11. Go deeper
| Resource | Why |
|---|---|
| 18 Observer | Broadcast without coordination. |
| 08 Facade | The outsider-facing look-alike. |
| System design track | Orchestration and sagas: mediators across services. |
| Wikipedia: Mediator pattern | Structure and examples. |