Mediator Pattern

Mediator Pattern

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

Intent

The Mediator centralises communication between a set of peer objects (Colleagues). Instead of each Colleague holding references to all other Colleagues and calling them directly, each Colleague holds only a reference to the Mediator. When a Colleague needs to notify a peer, it calls mediator.notify(this, event). The Mediator decides which Colleagues to coordinate and in what order. This removes the all-to-all coupling graph and replaces it with a hub-and-spoke topology: each Colleague knows only the hub; the hub knows everyone. The interaction logic becomes centralisable, inspectable, and modifiable without touching individual Colleagues.

When NOT to Use

  • Only two objects interact — a Mediator adds a third object and indirection for no coupling benefit; direct coupling is clearer
  • The interaction is simple and stable — the Mediator will become a thin pass-through with no value
  • The Mediator becomes a "God object" that knows everything about all Colleagues — this is the Mediator anti-pattern; if the mediator grows too large, decompose it using patterns like Command or Strategy

When to Use

  • Many objects interact in complex ways producing tight coupling — every Colleague references every other Colleague
  • Reusing an object is difficult because it communicates with many others — the Mediator extracts that communication so the Colleague is standalone
  • A centralised coordination policy is desirable (UI dialog orchestration, air traffic control analogy from GoF, chat room server, event bus)

How It Works

Participants: Mediator (interface — declares notify(sender: Colleague, event: string)), ConcreteMediator (implements coordination logic, holds references to all Colleague instances), Colleague (abstract or interface — holds a reference to its Mediator, calls mediator.notify(this, event) instead of calling peers), ConcreteColleague (specific peer objects, e.g. Button, TextInput, CheckBox in a dialog).

Colleagues are deliberately unaware of each other — only the ConcreteMediator knows the full interaction graph. Adding a new interaction means updating the ConcreteMediator, not touching individual Colleagues.

Class Diagram

classDiagram
    class Mediator {
        <<interface>>
        +notify(sender : Colleague, event : string)
    }
    class ConcreteMediator {
        -colleagueA : ColleagueA
        -colleagueB : ColleagueB
        +notify(sender : Colleague, event : string)
    }
    class Colleague {
        <<abstract>>
        #mediator : Mediator
        +setMediator(m : Mediator)
    }
    class ColleagueA {
        +operationA()
    }
    class ColleagueB {
        +operationB()
    }
    Mediator <|.. ConcreteMediator
    Colleague <|-- ColleagueA
    Colleague <|-- ColleagueB
    Colleague --> Mediator : notifies via
    ConcreteMediator --> ColleagueA : coordinates
    ConcreteMediator --> ColleagueB : coordinates

    note for ConcreteMediator "Encapsulates all colleague\ninteraction logic — colleagues\nnever reference each other"

TypeScript Example

// Mediator — TypeScript
// Source: GoF Design Patterns, p.273
 
interface Mediator {
  notify(sender: Component, event: string): void;
}
 
abstract class Component {
  constructor(protected mediator: Mediator) {}
  notify(event: string) { this.mediator.notify(this, event); }
}
 
class Button extends Component {
  click() { this.notify("click"); }
}
 
class TextInput extends Component {
  value = "";
  changed() { this.notify("changed"); }
}
 
class DialogMediator implements Mediator {
  constructor(private button: Button, private input: TextInput) {}
 
  notify(sender: Component, event: string): void {
    if (sender === this.input && event === "changed") {
      // Button enabled only when TextInput has content
      console.log(`Button enabled: ${this.input.value.length > 0}`);
    }
  }
}

Java Example

// Mediator — Java
// Source: GoF Design Patterns, p.273
 
interface Mediator {
    void notify(Component sender, String event);
}
 
abstract class Component {
    protected Mediator mediator;
    Component(Mediator m) { this.mediator = m; }
    void notify(String event) { mediator.notify(this, event); }
}
 
class Button extends Component {
    Button(Mediator m) { super(m); }
    void click() { notify("click"); }
}
 
class TextInput extends Component {
    String value = "";
    TextInput(Mediator m) { super(m); }
    void changed() { notify("changed"); }
}
 
class DialogMediator implements Mediator {
    private Button button;
    private TextInput input;
    DialogMediator(Button b, TextInput t) { this.button = b; this.input = t; }
 
    @Override
    public void notify(Component sender, String event) {
        if (sender == input && event.equals("changed")) {
            System.out.println("Button enabled: " + !input.value.isEmpty());
        }
    }
}

Mediator vs Observer Both patterns decouple senders from receivers, but structurally different:

  • Mediator: Colleagues know only the Mediator. The Mediator knows all colleagues. Communication is hub-and-spoke. One central object holds the interaction logic.
  • Observer: The Subject knows its Observers (at registration time). Observers subscribe to the Subject directly. Communication is broadcast (one-to-many). No central coordinator. Rule of thumb: if every object needs to know about every other object's state, use Mediator. If one object (Subject) notifies many observers of its own state changes, use Observer.
PatternRelationship
Observer-PatternBroadcast vs hub — see callout above; Observer is one-to-many broadcast, Mediator is hub-and-spoke coordination
Facade-PatternFacade simplifies access to a subsystem from clients; Mediator coordinates peer objects among themselves — different purposes despite both reducing coupling
Command-PatternCommand objects can be routed through a Mediator for additional decoupling between invokers and handlers
  • Vertical-Slice-Architecture — MediatR (the dominant VSA tooling pattern) dispatches requests to slice handlers using the Mediator pattern; the mediator is an optional implementation tactic, not a VSA requirement.
  • Chat-System-Design — the chat server is a classic Mediator: senders and receivers do not connect directly; all message exchange is mediated through the chat server pool; a direct real-world application of the pattern at scale

Sources

  • Gamma, Helm, Johnson, Vlissides — Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994, p.273

Notes that link here: GoF-Patterns-MOC