Mediator Pattern
1. What the Mediator pattern is
The Mediator pattern is a behavioural pattern that replaces the direct connections between a group of objects with a single hub they all talk to instead. Each object reports its events to the mediator; the mediator decides what everyone else should do in response. The objects never reference one another.
The Gang of Four names two roles. The Colleagues are the objects that need to coordinate: form widgets, chat participants, aircraft near an airport. The Mediator is the object that owns the coordination logic. A colleague that wants something to happen tells the mediator, and never reaches for its peers.
The point is topological. Let n colleagues all talk to each other directly and you get a mesh: up to connections, every one of them a compile-time dependency. Route everything through a mediator and the mesh collapses to a star: n connections, and the only object that knows the wiring is the mediator itself.
That is the whole trade: you concentrate the coupling rather than remove it. Section 5 covers when concentrating it is the wrong call.
2. The problem: directly coupled colleagues
Without a mediator, objects that must coordinate end up referencing each other directly, and the number of those references grows with the square of the object count. A registration dialog shows the cost concretely: a username field, a password field, an “I accept the terms” checkbox, and a submit button that should be clickable only when all three are valid. The rule is simple, but it is a cross-widget rule, and that is what makes the naive version painful.
Every widget that affects the button has to hold a reference to it, and it has to read every other widget to decide the button’s state:
public class Checkbox {
private final Button submit; // coupling
private final TextBox username; // coupling
private final TextBox password; // coupling
private boolean checked;
public void toggle() {
checked = !checked;
submit.setEnabled(checked
&& !username.text().isBlank()
&& password.text().length() >= 8);
}
}Each widget now carries the dialog’s validation rule. Three problems fall out of that:
- The coupling is quadratic. Every widget that participates in a rule needs a reference to every other widget the rule touches. Add a fourth field and you are editing the wiring in several classes.
- The rule is duplicated. That
submit.setEnabled(...)expression is the same logic intoggle(), in the username field’s change handler, and in the password field’s. Change the minimum password length and you change it in three places. - The widgets aren’t reusable. This
Checkboxonly compiles inside a dialog that has aButtonand twoTextBoxes. It is not a checkbox anymore; it is this dialog’s checkbox.
The validation rule doesn’t belong to any single widget. It belongs to the dialog, and that is the observation the Mediator pattern acts on.
3. Writing one in Java
3.1. The Mediator interface and the Component base
Two small abstractions carry the pattern. The Mediator interface is a single method: a colleague reports that something happened, and passes itself as the sender.
public interface Mediator {
void notify(Component sender, String event);
}The Component base class gives every widget a mediator reference and one protected helper to fire an event. This is the only thing the concrete widgets share, and the only thing they know about coordination.
public abstract class Component {
protected final Mediator mediator;
protected Component(Mediator mediator) {
this.mediator = mediator;
}
protected void changed(String event) {
mediator.notify(this, event);
}
}Note what is absent: a Component has no reference to any other Component. The coupling from §2 is deleted at the type level.
3.2. The colleagues
Each concrete widget does its own job and calls changed(...) when its state moves. It never decides what that change means for anyone else.
public class TextBox extends Component {
private String text = "";
public TextBox(Mediator mediator) {
super(mediator);
}
public void type(String text) {
this.text = text;
changed("text");
}
public String text() {
return text;
}
}The Button is the same shape. One detail matters: click() only fires an event when the button is enabled, so the “is the form valid” rule and the “did the user submit” rule stay separate.
public class Button extends Component {
private boolean enabled;
public Button(Mediator mediator) {
super(mediator);
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void click() {
if (enabled) {
changed("click");
}
}
}Checkbox follows the identical pattern with a toggle() method. None of the three reference the dialog type; they would compile and run under any Mediator.
3.3. The concrete mediator
RegistrationDialog implements Mediator. It owns the widgets, owns the validation rule, and is the only class that knows the dialog has the shape it has.
public class RegistrationDialog implements Mediator {
private final TextBox username = new TextBox(this);
private final TextBox password = new TextBox(this);
private final Checkbox acceptTerms = new Checkbox(this);
private final Button submit = new Button(this);
@Override
public void notify(Component sender, String event) {
if (sender == submit && event.equals("click")) {
register();
return;
}
// any field change re-evaluates whether submit may be enabled
boolean ready = !username.text().isBlank()
&& password.text().length() >= 8
&& acceptTerms.isChecked();
submit.setEnabled(ready);
}
private void register() {
// hand username and password off to the auth service...
}
}Passing this into each widget’s constructor wires the star. Every component points at the dialog and the dialog points back, but no component points at another. The validation rule lives in exactly one place. Adding a fourth field is one new component plus one clause in notify and no other class changes.
The event string keeps notify from collapsing into one undifferentiated handler. Here it only separates a submit click from a field edit, but in a larger dialog it is how the mediator tells “tab changed” from “text typed” from “row selected” apart without inspecting widget internals. A richer design might use an enum or one method per event; a plain string is enough to show the shape.
4. Mediator next to its neighbours
Mediator is easy to confuse with three other patterns, two of them behavioural. The distinctions are worth stating plainly.
Observer. Observer is one-to-many broadcast: a subject emits, any number of observers react, and the subject is indifferent to who is listening. Mediator is many-to-many coordination: the hub knows every colleague and decides, per event, what each should do. The two compose well, since a mediator often uses an observer mechanism to receive its colleagues’ events. The difference is the logic. Observer has none — it just delivers. A mediator’s whole reason to exist is that logic.
Facade. A facade also inserts one object in front of many, but the traffic is one-way: callers go through the facade into the subsystem, and the subsystem neither knows nor calls back. A mediator’s colleagues talk back to it, and it talks to them. Facade simplifies access; Mediator coordinates interaction.
Chain of Responsibility. Chain of Responsibility also decouples a sender from its handlers, but it routes one request along a line until something handles it. Mediator does not route; it holds the standing relationships between a fixed set of objects and reacts to their events. Chain answers who handles this one request; Mediator answers how this group stays consistent.
5. When not to use it
The mediator concentrates coupling; it does not delete it. When the coordination logic is genuinely large, that concentration produces a god object: a RegistrationDialog that grows to a thousand lines because every interaction rule in the dialog has nowhere else to live. The mesh was hard to read because it was spread out; the god-object mediator is hard to read because it is all in one place. Watch for a notify method that has turned into a long if/else ladder over event strings.
A few more cases where the pattern does not pay:
- Only two colleagues. Two objects that coordinate can just hold references to each other. A mediator for a pair is a hub with two spokes, which is pure ceremony.
- The interaction is a one-way pipeline. If A always triggers B which always triggers C, that is a chain or a plain sequence of calls, not a coordination problem. Mediator earns its place when the relationships are mutual and the rule is cross-cutting.
- The colleagues never need to be independent. Part of the payoff is reusable, dialog-agnostic widgets. If the widgets are single-use anyway, you are paying the indirection cost for a benefit you will not collect.
The honest rule of thumb: reach for a mediator when several objects interact in a many-to-many way, the interaction rule is cross-cutting and you want the colleagues to stay reusable in isolation. When the rule grows so large that the mediator itself becomes the new bottleneck, the answer is not “no mediator” but “more than one”. Split the dialog into independently mediated regions, each with a rule small enough to read.