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 --> MediatorWhen 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.
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
Click Run Go to execute. Runs via the Go Playground API.
Tradeoffs
| Pro | Con |
|---|---|
| Eliminates direct object-to-object references, making participants independently reusable | The mediator itself can become a god object that knows too much |
| Adding or removing participants requires changes in one place | Logic that feels natural spread across colleagues ends up concentrated in the mediator |
| Participants are independently testable with a mock mediator | Indirection makes call flow harder to follow in a debugger |
| Replaces a tightly coupled mesh with a single, inspectable routing point | A 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
notifyswitch. - 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
Userwith a nilmediatorfield silently drops messages. Either require a mediator at construction time or guard everySendcall, as shown above. - Avoid putting business logic inside
receivemethods on the colleague. Message handling belongs in the mediator or in a dedicated handler; colleagues should stay thin.
References
- Design Patterns: Mediator, GoF, the canonical definition
- Refactoring Guru: Mediator, diagrams and multiple-language examples
- SourceMaking: Mediator, additional context and known uses
Related topics
- 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