Mediator Pattern — Reduce Coupling Between Objects
What You'll Learn
You will learn how the Mediator pattern centralises communication between components, preventing spaghetti of inter-component references and making the system easier to maintain. You will contrast Mediator with Observer and Facade.
Why It Matters
In a complex UI, a button click might update a status bar, disable a menu item, log the action, and refresh a data grid. Wiring each component directly to every other component creates a dense web of dependencies. Mediator routes all communication through a single coordinator. With a Mediator, the button only knows about the Mediator — it doesn't know about the status bar, menu, logger, or data grid. Adding a new component that reacts to button clicks requires no changes to the button.
Real-World Use
Air traffic control — planes communicate with the tower (Mediator), not directly with each other. DodaTech's workflow engine uses a Mediator to coordinate step completion, error propagation, and status updates across pipeline stages without coupling stages to each other. When a new monitoring stage is added to the pipeline, it simply registers with the Mediator; existing stages never need to know about it.
The Pattern
Mediator declares the communication interface. Colleague classes hold a reference to the Mediator. ConcreteMediator orchestrates interactions between colleagues.
from abc import ABC, abstractmethod
class AirTrafficMediator(ABC):
@abstractmethod
def send_message(self, sender: str, message: str) -> str:
pass
@abstractmethod
def register_aircraft(self, name: str):
pass
class ControlTower(AirTrafficMediator):
def __init__(self):
self._aircraft = set()
def register_aircraft(self, name: str):
self._aircraft.add(name)
def send_message(self, sender: str, message: str) -> str:
responses = []
for ac in self._aircraft:
if ac != sender:
responses.append(f"{ac} received from {sender}: {message}")
return "\n".join(responses)
class Aircraft:
def __init__(self, name: str, mediator: AirTrafficMediator):
self._name = name
self._mediator = mediator
mediator.register_aircraft(name)
def send(self, message: str) -> str:
return self._mediator.send_message(self._name, message)
tower = ControlTower()
a1 = Aircraft("Flight 101", tower)
a2 = Aircraft("Flight 202", tower)
a3 = Aircraft("Flight 303", tower)
print(a1.send("Landing request, runway 4"))
Flight 202 received from Flight 101: Landing request, runway 4
Flight 303 received from Flight 101: Landing request, runway 4
Structure
classDiagram
class Mediator {
<>
+notify(sender, event)
}
class ConcreteMediator {
+notify(sender, event)
}
class Colleague1 {
+operation()
}
class Colleague2 {
+operation()
}
ConcreteMediator ..|> Mediator
ConcreteMediator --> Colleague1 : manages
ConcreteMediator --> Colleague2 : manages
Colleague1 --> Mediator : communicates via
Colleague2 --> Mediator : communicates via
Real-World Usage
- UI dialog components — a dialog Mediator enables/disables buttons, validates inputs, and shows error messages without buttons knowing about text fields.
- Python's
signalmodule — signals are mediated between senders and registered handlers. - Message brokers (RabbitMQ, Kafka) — decouple producers from consumers through a message-oriented Mediator.
- JavaScript event bus / pub-sub — Vue.js event bus or Node.js
EventEmitteract as mediators between components.
Related Patterns
- Observer enables one-to-many notification; Mediator centralises many-to-many communication.
- Facade simplifies a subsystem interface; Mediator coordinates subsystem components.
- Chain of Responsibility passes a request sequentially; Mediator broadcasts to multiple colleagues.
- Command encapsulates a request; Mediator decides which command to invoke.
Pros and Cons
| Pros | Cons |
|---|---|
| Reduces coupling between colleague classes | Mediator can become a god object with too many responsibilities |
| Centralises communication logic | Indirect communication can hurt performance |
| Easy to add new colleagues without changing others | Debugging is harder since the call flow is hidden |
| Simplifies protocols between components | Mediator's interface may need frequent changes |
The air traffic control example shows that Aircraft instances never communicate directly. They hold a reference to the ControlTower Mediator and send messages through it. The Mediator then broadcasts to all other aircraft. This decoupling means adding new aircraft (colleagues) requires no changes to existing aircraft classes.
Practice Questions
- Implement a chat room Mediator where users can send private messages to specific users and broadcast messages to all users.
- When does Mediator become an anti-pattern, and how would you decompose a large Mediator into smaller, focused ones?
- Compare Mediator with Observer — which would you use for a form validation system?
- How would you implement an undoable Mediator that can roll back interactions if a later step fails?
Challenge
Implement a Mediator for a modal dialog where three fields validate each other (e.g., start date must be before end date, end date must be after start date). The Mediator should coordinate validation across all three fields without the fields knowing about each other.
Real-World Task
Run DodaTech's dependency matrix on a UI-heavy module in your application. Identify components that reference three or more other components directly. Refactor the communication through a Mediator and re-run the matrix to measure coupling reduction.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro