Design Patterns · Behavioral Patterns
Mediator
Move cooperation rules out of tightly coupled components. Let components report events to a focused coordinator.
This lesson follows Iterator. Mediator addresses communication among components rather than traversal.
Mediator centralizes how a group of components cooperates. Components depend on the mediator contract instead of each other. The cost is moving coupling into the coordinator, which can grow too large.
Extract the relationships that block reuse
A shipping checkbox reveals an address field. A submit button checks that field before saving. If the checkbox and button call each other and the field directly, each reusable control carries rules for one particular form.
type DialogEvent = "shippingChanged" | "submit";
interface Mediator { notify(event: DialogEvent): void }
class ShippingCheckbox {
checked = false;
constructor(private mediator: Mediator) {}
toggle(checked: boolean) {
this.checked = checked;
this.mediator.notify("shippingChanged");
}
}
class AddressField { visible = false; value = ""; }
class SubmitButton {
constructor(private mediator: Mediator) {}
click() { this.mediator.notify("submit"); }
}
class ShippingDialog implements Mediator {
readonly shipping = new ShippingCheckbox(this);
readonly address = new AddressField();
readonly submit = new SubmitButton(this);
notify(event: DialogEvent): void {
if (event === "shippingChanged") {
this.address.visible = this.shipping.checked;
} else if (this.shipping.checked && !this.address.value.trim()) {
console.log("Address required");
} else {
console.log("Save form");
}
}
}
The controls know the Mediator interface. ShippingDialog knows the controls and the form rules. These controls still use shipping-specific events. A broader reusable control library would use a generic event contract with a sender or typed payload.
Follow an event through the coordinator
Try it
Toggle the shipping requirement
shippingChanged → dialog
address.visible = false
empty address does not block submit
shippingChanged → dialog
address.visible = true
empty address blocks submit
Step through
Submit with shipping enabled
The button reports an event
submit.click() → mediator.notify('submit')
The button does not inspect the address or call the checkbox.
The mediator applies the cooperation rule
shipping.checked = true
address.value is empty → Address required
The coordinator connects state from two components in one place.
The same control can join another coordinator
new SubmitButton(otherDialog)
The control's interaction stays the same. Another mediator can supply a different workflow.
Keep the coordinator bounded
Centralized communication still has coupling
A mediator that coordinates every screen, storage operation, and notification becomes difficult to change. Keep it focused on one cooperating group. Guard against feedback loops when updates trigger another notification.
Check yourself
One mediator owns every screen's interaction rules. What coupling remains?
Correct. Components gained independence, but the coordinator can become too large.
Not quite. The relationships still exist. Their owner changed.
Map the roles
Components must not know about each other. When something important happens to a component, it only tells the mediator. To a component, the mediator is a black box: the sender does not know who handles its event, and the receiver does not know who sent it.
| Role | In this example | Job |
|---|---|---|
| Components | ShippingCheckbox, AddressField, SubmitButton | Classes with business logic. Each holds a reference to the mediator, typed as the mediator interface, so it can be reused with a different mediator. |
| Mediator | Mediator | Declares how components communicate with it. Usually a single notification method, which can carry context such as the sender. |
| Concrete mediator | ShippingDialog | Encapsulates the relations between components. Often keeps references to all the components it manages, and sometimes controls their life cycle. |
Check yourself
When the submit button is clicked, what does it know about the address field?
Not quite. That direct link is exactly what the pattern removes.
Correct. The mediator decides which other components react.
Reach for it when components are tangled with their peers
- some classes are hard to change because they are tightly coupled to many other classes. Extract all the relationships into a separate class, so a change to one component stays isolated from the rest.
- you cannot reuse a component in another program because it depends too much on other components. After applying Mediator, components do not know about each other. To reuse one in a new app, give it a new mediator class.
- you are creating many component subclasses just to reuse basic behavior in different contexts. All relations between components live in the mediator, so you can define new ways for components to collaborate with a new mediator class, without touching the components.
Check yourself
A team made CheckoutSubmitButton, SignupSubmitButton, and ProfileSubmitButton only because each form validates differently. What does Mediator suggest?
Not quite. Subclassing per context is the smell this use case removes.
Correct. The differences are in cooperation rules, which belong in the mediator.
Implement it in six steps
Refactor existing code toward the pattern in this order:
- Identify a group of tightly coupled classes that would benefit from being more independent, for example to make them easier to maintain or reuse.
- Declare the mediator interface and the communication protocol between mediators and components. In most cases, one method for receiving notifications is enough. This interface is what lets you reuse components in different contexts.
- Implement the concrete mediator. It benefits from storing references to all the components it manages.
- Optionally, make the mediator responsible for creating and destroying components. It then starts to resemble a factory or a facade.
- Give each component a reference to the mediator, usually set in the component's constructor.
- Change the components so they call the mediator's notification method instead of methods on other components. Move the code that calls other components into the mediator, and run it when the mediator receives a notification.
Check yourself
In step 6, where does the line that hides the address field go?
Not quite. That keeps the checkbox coupled to the address field.
Correct. Components report events. The mediator holds the code that reacts to them.
Name what it costs
| You gain | You pay |
|---|---|
| Communication between components lives in one place, easier to understand and maintain (single responsibility) | Over time the mediator can grow into a god object |
| New mediators without changing the components (open/closed) | Events need a stable contract |
| Less coupling between components | Feedback loops are possible when a reaction triggers another notification |
| Individual components are easier to reuse |
Check yourself
A year later, AppMediator coordinates every screen, storage call, and notification. What happened?
Correct. Centralizing coordination does not remove coupling. Keep each mediator focused.
Not quite. One object that knows everything is the pattern's main risk, not its goal.
Do not confuse it with its neighbors
| Pattern | How it relates |
|---|---|
| Chain of Responsibility, Command, Observer | All four connect senders and receivers differently. Mediator removes direct connections between senders and receivers and forces them to talk through a mediator object. |
| Facade | Both organize collaboration among tightly coupled classes. A facade defines a simple interface to a subsystem, adds no behavior, and the subsystem does not know it exists. A mediator centralizes communication, and components know only the mediator. |
| Observer | The difference is easy to blur. Mediator's goal is to remove mutual dependencies among a set of components, which all depend on one mediator. Observer's goal is dynamic one-way connections, where some objects act as subordinates of others. A popular Mediator implementation uses Observer: the mediator publishes, and components subscribe. A mediator can also link components permanently, with no subscriptions at all. And a program where every component publishes to every other has no mediator, only distributed observers. |
Check yourself
Every component subscribes directly to every other component's events, with no central object. Mediator or Observer?
Correct. Mediator needs one central object that owns the cooperation rules.
Not quite. Communication alone does not make a mediator.
Retrieve and apply
Check yourself
A checkbox broadcasts changed to arbitrary listeners, and no central object coordinates their reactions. What pattern is most evident?
Correct. There are dynamic notification connections. Mediator requires a coordinator that owns the cooperation rules.
Not quite. Notifications alone do not identify a central coordination policy. The two patterns can be combined, but they are not the same intent.
Move one form rule from a reusable control into its coordinator. Continue to Memento to save private state without exposing it to history code.
Source: Alexander Shvets, Dive Into Design Patterns (深入设计模式), Chinese edition v2021-1.25. Mediator, printed pages 287–299. Explanations, examples, and exercises are adapted for this course.