behavioral
Mediator
Centralize many-to-many communication through one hub object.
Mediator defines an object that encapsulates how a set of objects interact. Peers do not reference each other directly; they reference the mediator, which is responsible for coordinating them. When many components need to react to each other — form fields that validate together, dialog buttons that enable based on multiple inputs — direct peer references turn an n×n interaction graph into an unmaintainable web. Mediator replaces it with a star: every peer knows one hub, and the hub knows every peer.
The problem
A signup dialog has a name field, an email field, a password field, a strength indicator, a terms checkbox, a submit button, and a cancel button. The submit button should be enabled only when name is non-empty, email contains '@', password meets policy, and terms is checked. The password strength updates as the password changes and disables submit on 'weak'. Direct wiring: each field holds references to the other six and updates them on change. The result is a component that must know every other component's API, is impossible to test in isolation, and breaks every time a field is added.
The solution
Introduce a `SignupMediator` that holds references to all the peers and implements the coordination: `onFieldChanged()` re-evaluates every constraint and updates the relevant peers. Each peer — fields, checkbox, buttons — takes the mediator in its constructor and calls `mediator.onFieldChanged()` on input. Peers no longer reference each other; they reference only the mediator. Adding a new field is one peer class and one method on the mediator. Testing a peer is independent of the others — you inject a fake mediator.
When to use it
- Many objects communicate in complex, changing patterns — n-to-n interactions are hard to reason about.
- Coordination logic is scattered across peers and needs a home.
- Peers should not know about each other, only about the coordination protocol.
- The interaction pattern is likely to change — a mediator localizes the change.
When not to use it
- There are two objects — a direct reference is simpler and clearer.
- The interaction is a simple pipeline — a Chain of Responsibility fits better.
- The mediator becomes a god object that contains all the logic — that is the anti-pattern Mediator tries to prevent, so use it carefully.
Analogy
An air traffic control tower: planes do not negotiate directly with each other for runway access — they talk to the tower, which coordinates landings, takeoffs, and holds. Every plane knows one frequency; the tower knows every plane. Removing the tower would require every pilot to talk to every other pilot at once.
Diagram
Implementation
interface Mediator {
notify(sender: Peer, event: string): void;
}
abstract class Peer {
protected mediator!: Mediator;
setMediator(mediator: Mediator): void {
this.mediator = mediator;
}
}
class NameField extends Peer {
value = "";
setValue(v: string): void {
this.value = v;
this.mediator.notify(this, "changed");
}
}
class EmailField extends Peer {
value = "";
setValue(v: string): void {
this.value = v;
this.mediator.notify(this, "changed");
}
}
class PasswordField extends Peer {
value = "";
setValue(v: string): void {
this.value = v;
this.mediator.notify(this, "changed");
}
}
class StrengthIndicator extends Peer {
strength: "weak" | "medium" | "strong" = "weak";
show(s: "weak" | "medium" | "strong"): void {
this.strength = s;
}
}
class TermsCheckbox extends Peer {
checked = false;
setChecked(v: boolean): void {
this.checked = v;
this.mediator.notify(this, "changed");
}
}
class SubmitButton extends Peer {
enabled = false;
setEnabled(v: boolean): void {
this.enabled = v;
}
}
class SignupMediator implements Mediator {
constructor(
private readonly name: NameField,
private readonly email: EmailField,
private readonly password: PasswordField,
private readonly strength: StrengthIndicator,
private readonly terms: TermsCheckbox,
private readonly submit: SubmitButton,
) {}
notify(sender: Peer, event: string): void {
if (event !== "changed") return;
if (sender === this.password) {
this.strength.show(this.computeStrength(this.password.value));
}
const valid =
this.name.value.trim().length > 0 &&
this.email.value.includes("@") &&
this.strength.strength !== "weak" &&
this.terms.checked;
this.submit.setEnabled(valid);
}
private computeStrength(pwd: string): "weak" | "medium" | "strong" {
if (pwd.length < 8) return "weak";
if (/[A-Z]/.test(pwd) && /\d/.test(pwd)) return "strong";
return "medium";
}
}Every peer holds the mediator, not each other. NameField.setValue notifies the mediator and does nothing else. The mediator owns the coordination — 'when password changes, update strength indicator; when anything changes, re-evaluate submit validity'. Adding a 'confirm password' field is a new Peer and one line in the mediator; no existing peer changes. Testing NameField is trivial — inject a fake mediator and assert notify() was called. The god-object risk is real but is contained: the mediator's job is coordination, not business logic, and it can be decomposed if it grows too large.
Advantages
- Reduces a many-to-many graph to a star — every peer knows one hub.
- Coordination logic is centralized and testable as a unit.
- Peers become independent — adding or removing one is a small change to the mediator.
- Testing a peer requires only a fake mediator, not the other seven peers.
Disadvantages
- The mediator can become a god object if coordination logic grows unbounded.
- Every peer must go through the mediator even for trivial interactions.
- The mediator is a single point of change — modifying its coordination affects every peer.
- Debugging requires following the notify() calls into a single dense method.
Related patterns
- Observer — Both centralize communication. Observer is one subject broadcasting to many subscribers; Mediator is many peers coordinating through one hub. A mediator often uses Observer internally.
- Facade — Both provide a centralized interface. Facade simplifies a subsystem for external callers; Mediator coordinates internal peers. Facade does not add behavior; Mediator does.
- Chain of Responsibility — Both decouple senders from handlers. Chain forwards linearly until one handler takes it; Mediator coordinates many peers centrally. Chain is a line, Mediator is a star.