Low-level designDeep dive8 min
Mediator
When many objects must react to each other, give their interactions to one object in the middle. Each object tells the mediator what happened; the mediator decides who does what, so the objects stop knowing about each other.
Devices that react to each other
The projector dims the lights and closes the blinds. The sensor turns everything off. The lights tell the projector when they go to full.
1 · The situation
A meeting room whose devices talk to each other
An office's meeting rooms are automated. When the projector starts, the lights dim and the blinds close. If someone presses the light switch during a presentation, the projector switches to bright mode to stay readable. When the occupancy sensor sees the room empty, everything turns off and the blinds open.
The first version wires the devices to each other: Projector holds the lights and the blinds, Lights holds the projector, and Sensor holds all three. Printing stands in for controlling real hardware. Python writes press_full() and room_empty().
class Blinds {
void close() {
System.out.println(" blinds closed");
}
void open() {
System.out.println(" blinds open");
}
}
class Lights {
private Projector projector;
void connect(Projector projector) {
this.projector = projector;
}
void dim() {
System.out.println(" lights at 30%");
}
void off() {
System.out.println(" lights off");
}
void pressFull() {
System.out.println(" lights at 100%");
if (projector.running()) projector.boost();
}
}
class Projector {
private final Lights lights;
private final Blinds blinds;
private boolean running;
Projector(Lights lights, Blinds blinds) {
this.lights = lights;
this.blinds = blinds;
}
void start() {
running = true;
System.out.println(" projector on");
lights.dim();
blinds.close();
}
void boost() {
System.out.println(" projector in bright mode");
}
void stop() {
running = false;
System.out.println(" projector off");
}
boolean running() {
return running;
}
}
class Sensor {
private final Projector projector;
private final Lights lights;
private final Blinds blinds;
Sensor(Projector projector, Lights lights, Blinds blinds) {
this.projector = projector;
this.lights = lights;
this.blinds = blinds;
}
void roomEmpty() {
projector.stop();
lights.off();
blinds.open();
}
}
public class Main {
public static void main(String[] args) {
Lights lights = new Lights();
Blinds blinds = new Blinds();
Projector projector = new Projector(lights, blinds);
lights.connect(projector);
Sensor sensor = new Sensor(projector, lights, blinds);
System.out.println("Meeting starts:");
projector.start();
System.out.println("Someone presses the light switch:");
lights.pressFull();
System.out.println("Everyone leaves:");
sensor.roomEmpty();
}
}The class diagram of this version, drawn from its code: a web of devices that reference each other.
A meeting starts, someone turns the lights up, and everyone leaves:
Meeting starts: projector on lights at 30% blinds closed Someone presses the light switch: lights at 100% projector in bright mode Everyone leaves: projector off lights off blinds open
2 · The change
One more device, edits in two others
Facilities wants a sign on the door that says "In use" during a meeting and "Free" once the room is empty. In this design the sign has to be known by every device whose action changes it:
class Projector {
private final Lights lights;
private final Blinds blinds;
private boolean running;
Projector(Lights lights, Blinds blinds) {
this.lights = lights;
this.blinds = blinds;
}
void start() {
running = true;
System.out.println(" projector on");
lights.dim();
blinds.close();
}
void boost() {
System.out.println(" projector in bright mode");
}
void stop() {
running = false;
System.out.println(" projector off");
}
boolean running() {
return running;
}
}Device | Knows about today | With the door sign |
|---|---|---|
| Lights, blinds | Lights, blinds, door sign |
| Projector | Projector |
| Projector, lights, blinds | Projector, lights, blinds, door sign |
| Nothing | Nothing |
The rules of the room are spread across the devices: what happens when a meeting starts is in Projector, what happens when it ends is in Sensor. Lights and Projector reference each other, so neither can be reused or tested alone, and the checks point out the two-way link. Each new device or rule touches every device involved, and the web gets denser.
3 · The refactor
Put the room's rules in one object
Start from the first version. Let devices report what happened to the room, and let the room decide what every other device does:
RoomEvents names what devices report: a meeting started, the lights were pressed to full, the room emptied. Each device holds only a RoomEvents and calls it after its own action; none of them references another device.
class Projector {
private final RoomEvents room;
private boolean running;
Projector(RoomEvents room) {
this.room = room;
}
void start() {
running = true;
System.out.println(" projector on");
room.meetingStarted();
}
void boost() {
System.out.println(" projector in bright mode");
}
void stop() {
running = false;
System.out.println(" projector off");
}
boolean running() {
return running;
}
}
class Lights {
private final RoomEvents room;
Lights(RoomEvents room) {
this.room = room;
}
void dim() {
System.out.println(" lights at 30%");
}
void off() {
System.out.println(" lights off");
}
void pressFull() {
System.out.println(" lights at 100%");
room.lightsPressedFull();
}
}After the second step the program prints what it printed before. After the third, the sign follows the meeting:
Meeting starts: projector on lights at 30% blinds closed door sign: In use Someone presses the light switch: lights at 100% projector in bright mode Everyone leaves: projector off lights off blinds open door sign: Free
References from one device to another. Before: six, including the two-way link. After: none; each device references only the room's interface.
4 · The pattern
Mediator: one object that coordinates the rest
Mediator defines an object that encapsulates how a set of objects interact. The objects, the colleagues, don't refer to each other; each tells the mediator what happened, and the mediator calls the colleagues that should react. Many-to-many links become many-to-one.
The roles in the room's code: RoomEvents is the mediator interface, Room the concrete mediator, and each device a colleague that knows only the interface.
Role | In the meeting room | Its job |
|---|---|---|
Mediator |
| What colleagues can report |
Concrete mediator |
| Holds the colleagues and the rules for how they react to each other |
Colleagues |
| Do their own job, and report events to the mediator |
The projector reports one event; the room decides which devices react, and how.
Colleagues know the mediator through an interface, and the mediator knows the colleagues by class. That keeps the dependencies pointing one way: the checks find no cycle, and each device can be tested with a fake RoomEvents.
5 · What it costs
A mediator can become the new tangle
- The mediator grows. Every rule of the room lives in
Room. With dozens of devices and rules it becomes a god object, the complexity moved rather than removed. Split it by concern, such as lighting and presentation, before it gets there. - One more hop. Reading what pressing the light switch does means going through the mediator.
- Two-way knowledge remains. Colleagues call the mediator and the mediator calls them. An interface on one side keeps it from being a dependency cycle, but both sides still have to agree on the events.
Situation | Mediator? |
|---|---|
Several objects react to each other in many-to-many ways | Yes |
One object announces, many react, none reply | Observer is simpler |
Two objects talk to each other | No: let them |
The rules grow without limit | Split the mediator, or use events and an event bus |
6 · Getting it right
Report events, keep the rules central
- Colleagues report what happened, not what to do.
meetingStarted()is a fact;dimLightsAndCloseBlinds()would put the rule back in the projector. - Keep each colleague usable alone. Its own job, such as dimming, stays in the colleague; only the coordination moves.
- Give colleagues the mediator through an interface, so they can be tested with a fake and no dependency cycle forms.
- Watch the mediator's size. When it has rules for unrelated concerns, split it.
Language | The usual shape |
|---|---|
Java | A mediator interface the colleagues hold, and one class implementing it that holds the colleagues |
C++ | The same; colleagues hold a reference to the mediator interface, and the mediator non-owning pointers to the colleagues |
JavaScript | An object with the event methods; or an event emitter, trading named methods for event names |
Python | A |
7 · Look-alikes
Objects in the middle
Several patterns place an object between others. What differs is who calls it and what it knows:
Pattern | Who calls the object in the middle | What it knows |
|---|---|---|
Mediator | The colleagues themselves | All the colleagues, and the rules between them |
Observer | The subject, to notify | Only a list of observers, not what they do |
Facade | Outside clients | A subsystem that never calls it back |
Event bus | Anyone, posting events by type | Nothing: it routes events to subscribers |
Mediator and Facade are both the one place that knows many classes. A facade is a front door: clients use it, and the classes behind it don't know it exists. A mediator is a hub for its colleagues, who call it, and are called by it, all the time.
8 · In practice
Hubs in everyday code
User interfaces use mediators constantly. A form's controller listens to its fields and decides that the Submit button is enabled only when every field is valid, so the fields never refer to the button or to each other. The same shape appears in a game's level controller, or the room controller of a booking system.
The practice checks have no Mediator detector. On this page they show the problem rather than the pattern: the first version's two-way link between Lights and Projector is reported, and it's gone after the refactor.
Check yourself
4 questions
Takeaways
Remember this
- Mediator moves the interactions between many objects into one object they all report to.
- Colleagues report what happened; the mediator decides who reacts and how.
- Many-to-many links become many-to-one, and each colleague can be tested alone.
- Give colleagues the mediator through an interface to avoid a dependency cycle.
- A mediator that keeps growing becomes a god object: split it by concern.