Mediator Pattern: Centralizing Communication Between Components
In many backend systems, different modules or services need to talk to each other.
If each component talks to every other component directly, you end up with a spaghetti mess of dependencies that’s hard to maintain.
The Mediator Pattern solves this by introducing a central authority (the mediator) that coordinates communication between components.
Instead of components talking to each other directly, they send messages to the mediator, which then routes or coordinates the responses.
What is the Mediator Pattern?
The Mediator Pattern defines an object that encapsulates how a set of objects interact.
By centralizing communication, it reduces dependencies between components, making the system easier to maintain and extend.
Real-World Analogy: Air Traffic Control
In an airport, planes don’t talk to each other directly to avoid collisions.
Instead, they all communicate with Air Traffic Control (ATC):
- ATC (mediator) coordinates takeoffs, landings, and flight paths.
- Each plane (colleague component) only needs to know how to talk to ATC, not to other planes.
This central authority keeps operations safe and avoids chaos.
When to Use It in Backend Development
You might use the Mediator pattern when:
- Many objects/components need to communicate but you want to avoid tight coupling.
- You want to centralize business rules and coordination logic in one place.
- You’re building chat rooms, event hubs, or workflow engines.
Common backend examples:
- Message brokers (RabbitMQ, Kafka, NATS)
- Event dispatchers inside a monolith
- Orchestration services in microservices
Example: Mediator for a Chat Room System
Let’s simulate a chat room where users can send messages to each other, but all messages go through a mediator.
Step 1: Mediator Interface
from abc import ABC, abstractmethod class Mediator(ABC): @abstractmethod def send_message(self, sender, message): pass
Step 2: Concrete Mediator
class ChatRoomMediator(Mediator): def __init__(self): self._participants = [] def register(self, participant): self._participants.append(participant) def send_message(self, sender, message): for participant in self._participants: if participant != sender: participant.receive(message, sender)
Step 3: Colleague Components
class Participant: def __init__(self, name, mediator: Mediator): self.name = name self.mediator = mediator mediator.register(self) def send(self, message): print(f"[{self.name}] Sending: {message}") self.mediator.send_message(self, message) def receive(self, message, sender): print(f"[{self.name}] Received from {sender.name}: {message}")
Step 4: Using the Mediator
if __name__ == "__main__": chat_mediator = ChatRoomMediator() alice = Participant("Alice", chat_mediator) bob = Participant("Bob", chat_mediator) charlie = Participant("Charlie", chat_mediator) alice.send("Hi everyone!") bob.send("Hey Alice!")
Output:
[Alice] Sending: Hi everyone! [Bob] Received from Alice: Hi everyone! [Charlie] Received from Alice: Hi everyone! [Bob] Sending: Hey Alice! [Alice] Received from Bob: Hey Alice! [Charlie] Received from Bob: Hey Alice!
Why It Works
- Reduced Dependencies: Participants only depend on the mediator, not on each other.
- Centralized Logic: The mediator controls how messages are routed.
- Flexibility: Adding/removing participants is easy without changing the others.
Real Backend Use Case: Django’s signals + Central Event Bus
In larger Django apps, instead of having models call each other directly, you can have them send events to a central event dispatcher (mediator) which notifies other parts of the system.
Example:
class EventBus: def __init__(self): self.subscribers = {} def subscribe(self, event_type, handler): self.subscribers.setdefault(event_type, []).append(handler) def publish(self, event_type, data): for handler in self.subscribers.get(event_type, []): handler(data)
This is a mediator coordinating interactions between decoupled parts.
Gotchas and Considerations
1. Mediator Can Become a God Object
If you’re not careful, the mediator ends up containing all the logic and becomes bloated.
Keep the mediator focused on coordination, not business rules.
2. Single Point of Failure
If the mediator crashes or misbehaves, communication stops entirely.
In distributed systems, make the mediator highly available (e.g., clustered message broker).
3. Debugging Indirect Communication
It can be harder to trace communication because messages are routed indirectly.
Use structured logging inside the mediator to track message flow.
4. Extra Indirection Overhead
Sometimes direct communication is simpler and faster.
Only use Mediator if decoupling benefits outweigh the added complexity.
Conclusion
The Mediator Pattern centralizes communication, reducing direct dependencies and making your system easier to maintain.
It’s especially powerful in complex backend architectures where many components need to interact without forming a tangled web of connections.
In the next Behavioral pattern, we’ll explore the Memento Pattern, which helps capture and restore an object’s state without violating encapsulation.