Mediator

Advanced · 10 min read · ▶ live playground · ✦ checkpoint

A support chat begins with two participants who call each other directly. Add a supervisor, a bot, and transfer rules, and every participant gains more references plus more knowledge about who should react.

The Mediator pattern moves coordination for a group of objects into one mediator object. It replaces many-to-many references with a central routing boundary, but that boundary can become a large, highly coupled coordinator if every business rule moves into it.

Before: every participant knows the room

Direct references look harmless with two people:

class Customer {
  public agent: SupportAgent | null = null;
 
  public ask(message: string): void {
    this.agent?.receiveFromCustomer(message);
  }
 
  public receiveFromAgent(message: string): void {
    console.log(`customer received: ${message}`);
  }
}
 
class SupportAgent {
  public customer: Customer | null = null;
 
  public reply(message: string): void {
    this.customer?.receiveFromAgent(message);
  }
 
  public receiveFromCustomer(message: string): void {
    console.log(`agent received: ${message}`);
  }
}

Now a transfer must disconnect one agent, connect another, notify a supervisor, and update a transcript. That coordination lands inside Customer and SupportAgent, even though neither object owns the room.

With n participants, each object can accumulate references to the other n - 1 participants. Quadratic coupling means the potential number of directed connections grows like n(n - 1), often described as n² coupling. Adding one role changes several existing classes.

After: colleagues communicate through one mediator

A mediator is an object that centralizes how a set of objects interact. A colleague is one object in that set; it sends requests through the mediator instead of naming all other colleagues directly.

Customer and SupportAgent both hold the same Mediator contract. ChatRoom receives a message and routes it to a Participant, leaving each colleague unaware of the other's concrete class.

class diagram

Chat colleagues route messages through one mediator

Chat colleagues route messages through one mediator
  1. 1Mediator gives every colleague one route for sending a message.
  2. 2The mediator routes between Participant references instead of concrete colleague types.
  3. 3ChatRoom implements the routing contract and becomes the interaction hub.
  4. 4Customer is one colleague that can receive a routed message.
  5. 5Customer holds the mediator, not a field for every agent, bot, or supervisor.
  6. 6SupportAgent follows the same participant contract as the customer.
  7. 7SupportAgent also sends through the mediator, keeping both colleague edges consistent.

The two has edges are the important replacement for the old web of references. Colleagues know the mediator; they do not own fields for each concrete recipient. ChatRoom may route, reject, log, or redirect a message at one visible boundary.

interface Participant {
  readonly name: string;
  receive(from: Participant, message: string): void;
}
 
interface Mediator {
  send(from: Participant, to: Participant, message: string): void;
}
 
class ChatRoom implements Mediator {
  public send(
    from: Participant,
    to: Participant,
    message: string,
  ): void {
    console.log(`room routes ${from.name} -> ${to.name}`);
    to.receive(from, message);
  }
}
 
class Customer implements Participant {
  public constructor(
    public readonly name: string,
    private mediator: Mediator,
  ) {}
 
  public send(to: Participant, message: string): void {
    this.mediator.send(this, to, message);
  }
 
  public receive(from: Participant, message: string): void {
    console.log(`${this.name} received from ${from.name}: ${message}`);
  }
}
 
class SupportAgent implements Participant {
  public constructor(
    public readonly name: string,
    private mediator: Mediator,
  ) {}
 
  public send(to: Participant, message: string): void {
    this.mediator.send(this, to, message);
  }
 
  public receive(from: Participant, message: string): void {
    console.log(`${this.name} received from ${from.name}: ${message}`);
  }
}
 
const room: Mediator = new ChatRoom();
const mina = new Customer("Mina", room);
const dev = new SupportAgent("Dev", room);
 
mina.send(dev, "Is order 42 delayed?");
dev.send(mina, "It ships today.");

room routes Mina -> Dev Dev received from Mina: Is order 42 delayed? room routes Dev -> Mina Mina received from Dev: It ships today.

Neither colleague branches on the recipient's class. Both calls cross ChatRoom, which chooses the route and then invokes the recipient's shared receive() operation.

This small mediator delegates display behavior back to the recipient. That division matters: the room coordinates the interaction, while colleagues retain behavior that belongs to them. A mediator should not automatically absorb every operation performed after a message arrives.

Mediator versus Facade

Both patterns can place one object in front of several collaborators, but the direction of communication differs.

  • Mediator centralizes many colleagues' interaction. Colleagues send through it, receive messages from it, and may trigger further coordination.
  • Facade offers one-way simplification over a subsystem. A client calls the facade; subsystem objects do not normally call back through it to coordinate with each other.

ChatRoom is a mediator because participants actively communicate through it. A SupportFacade.openTicket() that calls a database, mailer, and audit service behind one small client API is a facade. It hides subsystem complexity rather than replacing a many-to-many collaboration.

When NOT to use it

For two objects with one stable interaction, a direct reference is easier to follow. A reader can move from the call to its receiver without finding the mediator and reconstructing a routing rule.

The central risk is a god object, a class that accumulates too many unrelated responsibilities and knows too much about the rest of the system. A mediator can become one by collecting routing, validation, billing, persistence, formatting, and every colleague's domain decisions. Split mediators by interaction boundary or return domain work to colleagues before that happens.

The hub can also become a bottleneck for change and testing. Every new interaction edits the same file, and a failure there affects the whole group. Mediator trades a web of peer dependencies for one central dependency; use it when the many-to-many coordination is real and give the hub a narrow job.

Checkpoint

Answer all three to mark this lesson complete

Mediator controls how several objects talk now. Next, Memento captures what one object looked like earlier so it can return to that state without exposing its internals.

+50 XP on completion