Skip to content

Mediator Pattern

The problem

As a system grows, objects accumulate direct references to each other. A chat room where every User holds a list of other User instances is the classic example: adding a new user means updating every existing user’s contact list, removing a user means hunting down every reference, and broadcasting a message means iterating over every held reference. The web of object-to-object connections becomes a maintenance and debugging burden. Each object now depends on the concrete types of all its peers.

Mediator cuts those direct connections by routing communication through a central object. Each participant only knows about the mediator. When a user sends a message, it hands that message to the ChatRoom, which decides who receives it. Users are unaware of each other. Adding or removing a participant is a change to the mediator alone, not to every other object in the graph.

Structure

classDiagram
class Mediator {
<<interface>>
+notify(sender, event)
}
class ChatRoom {
-participants: User[]
+register(user)
+notify(sender, event)
}
class Colleague {
<<interface>>
+setMediator(mediator)
+send(message)
+receive(from, message)
}
class User {
-name: string
-mediator: Mediator
+setMediator(mediator)
+send(message)
+receive(from, message)
}
Mediator <|-- ChatRoom
Colleague <|-- User
ChatRoom --> User
User --> Mediator

When to use

  • A set of objects communicate in well-defined but complex ways, and the resulting dependencies are hard to understand or change.
  • Reusing an object is difficult because it holds references to many other objects.
  • Behavior distributed across several classes should be customizable without heavy subclassing.
  • You want to replace a many-to-many object graph with a one-to-many hub-and-spoke topology.

Implementation

ChatRoom holds the participant list and routes every message, so each User calls chatRoom.send(this, message) and never touches another User directly. Python uses a Protocol to describe the mediator contract without forcing ChatRoom to inherit from anything concrete, and each User stores a mediator reference only after register is called. In Go, interfaces are satisfied implicitly, so ChatRoom satisfies Mediator without any declaration; a User created without calling Register has a nil mediator, and Send guards against that to avoid a panic.

idle
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).

Tradeoffs

ProCon
Eliminates direct object-to-object references, making participants independently reusableThe mediator itself can become a god object that knows too much
Adding or removing participants requires changes in one placeLogic that feels natural spread across colleagues ends up concentrated in the mediator
Participants are independently testable with a mock mediatorIndirection makes call flow harder to follow in a debugger
Replaces a tightly coupled mesh with a single, inspectable routing pointA mediator with many special cases is harder to reason about than the mesh it replaced

Gotchas

  • The mediator pattern solves coupling, not complexity. If the routing logic is intricate, the mediator will be intricate. Factor complex routing into smaller, named methods rather than one large notify switch.
  • Observer and Mediator are often confused. Observer lets publishers broadcast to unknown subscribers. Mediator centralizes control: it knows all participants and decides who hears what. If the mediator is just re-broadcasting everything, you probably want Observer instead.
  • Bidirectional coupling is easy to introduce accidentally: if the mediator calls a method on a colleague that then calls back into the mediator, you have a cycle. Keep the flow unidirectional: colleagues call the mediator, the mediator calls colleagues.
  • In Go, a zero-value User with a nil mediator field silently drops messages. Either require a mediator at construction time or guard every Send call, as shown above.
  • Avoid putting business logic inside receive methods on the colleague. Message handling belongs in the mediator or in a dedicated handler; colleagues should stay thin.

References

  • Design Patterns, the full GoF catalog
  • Observer, Observer distributes communication to unknown subscribers; Mediator centralizes it to a known set
  • Facade, Facade simplifies a subsystem interface; Mediator coordinates peers that talk to each other