| Lesson 11 | Mediator: structure |
| Objective | Identify the participants in the Mediator pattern and trace how they coordinate an interaction. |
The Mediator pattern is a behavioral design pattern that encapsulates how a cohesive set of objects interact. Instead of every participating object knowing and invoking several peers, colleagues report events or requests through a mediator. The mediator applies the collaboration rules and invokes the appropriate colleagues.
Mediator does not remove all coupling. It replaces many concrete colleague-to-colleague dependencies with a dependency on a mediator. Interaction complexity remains, but it is localized for inspection, testing, and deliberate change. Colleague is the classic Gang of Four term; production code often uses names such as component, control, device, participant, or service.
A contract can be generic, such as componentChanged(source, event), or domain-specific, such as requestLanding(aircraft). Generic operations accommodate new event types but may require runtime branching. Domain-specific methods express clearer intent but can enlarge the contract.
| Participant | Usually knows | Should not need to know |
|---|---|---|
| Mediator interface | The operations colleagues use to communicate | Concrete colleague instances or storage details |
| Concrete mediator | Participating colleagues and collaboration rules | Unrelated domain behavior outside the collaboration |
| Colleague abstraction | The mediator contract or a narrow communication port | Every concrete peer type |
| Concrete colleague | Its own state, behavior, and mediator-facing events | The complete interaction graph and global coordination policy |
The pattern rearranges dependency direction. Directly connected colleagues reference peers and embed assumptions about them. Mediated colleagues usually depend on a mediator abstraction, while the concrete mediator is composed with the participants. A client or composition root connects the objects.
A concrete mediator does not always need a field for every colleague. Fixed collaborations often use named fields; dynamic systems may use registration, lookup tables, groups, or routing metadata. A colleague can pass itself with an event, or a typed callback can identify the source.
The diagram places a mediator between front-end and back-end colleagues. Front-end participants might be controls or views; back-end participants might be domain objects, services, repositories, or models. The mediator receives an event, determines what it requires, and coordinates only the affected participants.
In a word processor, moving a scroll bar can update a viewport without changing stored text. Editing text can update the model and refresh appropriate views. This is selective coordination, not indiscriminate broadcasting. Mediator can also coordinate dialog controls, aircraft, workflow steps, smart-home devices, or chat participants.
Steps four and five require a reentrancy policy because one action can produce another event during processing. Safeguards include separating commands from notifications, suppressing unchanged values, queuing nested events, or modeling legal state transitions. Otherwise, the collaboration can enter a cycle.
Events should communicate intent. emailChanged() is clearer than notify("changed"), and submitRequested() is clearer than arbitrary strings. Typed events reduce errors and reveal supported interactions.
In this example, an email field and submit button never reference each other. Both report through a mediator. The concrete mediator enables the button only when the field contains a plausible address and processes a submission request.
public final class MediatorStructureDemo {
enum Event {
EMAIL_CHANGED,
SUBMIT_REQUESTED
}
interface Mediator {
void componentChanged(Component source, Event event);
}
abstract static class Component {
private final Mediator mediator;
protected Component(Mediator mediator) {
this.mediator = mediator;
}
protected final void report(Event event) {
mediator.componentChanged(this, event);
}
}
static final class EmailField extends Component {
private String text = "";
EmailField(Mediator mediator) {
super(mediator);
}
void enterText(String text) {
this.text = text;
report(Event.EMAIL_CHANGED);
}
String text() {
return text;
}
}
static final class SubmitButton extends Component {
private boolean enabled;
SubmitButton(Mediator mediator) {
super(mediator);
}
void setEnabled(boolean enabled) {
this.enabled = enabled;
System.out.println("Submit enabled: " + enabled);
}
void click() {
if (enabled) {
report(Event.SUBMIT_REQUESTED);
} else {
System.out.println("Submission blocked.");
}
}
}
static final class RegistrationForm implements Mediator {
private final EmailField emailField = new EmailField(this);
private final SubmitButton submitButton = new SubmitButton(this);
EmailField emailField() {
return emailField;
}
SubmitButton submitButton() {
return submitButton;
}
@Override
public void componentChanged(Component source, Event event) {
if (source == emailField && event == Event.EMAIL_CHANGED) {
submitButton.setEnabled(emailField.text().contains("@"));
} else if (source == submitButton && event == Event.SUBMIT_REQUESTED) {
System.out.println("Registered: " + emailField.text());
}
}
}
public static void main(String[] args) {
RegistrationForm form = new RegistrationForm();
form.submitButton().click();
form.emailField().enterText("alice@example.com");
form.submitButton().click();
}
}
Submission blocked.
Submit enabled: true
Registered: alice@example.com
Mediator is the contract, and RegistrationForm is the concrete mediator that owns the colleagues and form policy. Component is an optional colleague base class. EmailField and SubmitButton are concrete colleagues. The main method is the client.
The contains("@") check is intentionally minimal, not production validation. This is Mediator rather than Observer because the button does not subscribe to the field and the field does not broadcast to dependents. Both use RegistrationForm, which applies interaction policy.
Mediator has a less rigid inheritance structure than some patterns. Colleagues communicate through a mediator, the mediator owns interaction policy, and colleagues retain local responsibilities. Production code does not require four source files or literal GoF class names. Preserve responsibilities and dependency direction.
A concrete mediator can create fixed colleagues, as in the example, or receive them from a composition root. Dynamic participants can be registered and removed. Constructor self-registration can obscure ownership, expose a partially constructed object, and complicate removal.
The concrete mediator is coupled to the operations it coordinates and can become too large. Centralized policy can hide runtime effects. A synchronous mediator can become a failure or performance bottleneck. Dynamic registration introduces lifecycle concerns, while concurrency requires rules for synchronization, ordering, cancellation, and failure isolation.
Mediator reduces direct interconnections and reallocates responsibilities. It may reduce subclassing used only to change coordination, but that is not a defining guarantee.
Before mediation, colleagues may contain direct references to one another, producing a dense graph. In a fully connected group of n colleagues, potential pairs grow approximately as n(n - 1) / 2. Mediation replaces many peer relationships with roughly one per colleague, while the concrete mediator bears the cost of understanding the collaboration.
The mediator is more than a target interface; it makes routing, validation, and sequencing decisions. Retain direct relationships when they are simple and stable. Introduce Mediator when many-object interaction policy causes change or complexity.
Loose coupling limits concrete knowledge and change propagation. A colleague can change without forcing every peer to change when its mediator contract remains stable. Coupling still exists: colleagues depend on the contract, and the concrete mediator depends on required colleague operations. The goal is intentional, stable dependencies.
| Pattern | Primary purpose | Difference from Mediator |
|---|---|---|
| Observer | Notify dependents when a subject changes | Mediator owns collaboration policy among peers; Observer distributes state-change notifications to subscribers. |
| Facade | Provide a simpler entry point to a subsystem | A facade is mainly used from outside a subsystem; a mediator coordinates participating objects. |
| Command | Represent a request as an object | A mediator may route commands, but Command encapsulates a request and can support queuing, logging, or undo. |
| Controller | Handle input or application flow in an architectural role | A controller can act as a mediator, but not every controller represents the GoF collaboration structure. |
Pattern names describe responsibilities, not framework annotations. One class can participate in more than one pattern when its responsibilities remain explicit and cohesive.
A sound Mediator structure gives each participant a clear reason to change. Colleagues change when their local behavior changes, while the mediator changes when collaboration policy changes. That separation, rather than a prescribed inheritance hierarchy, is the pattern's central structural value.