Mediator Pattern: Centralizing Communication Between Components
By Misbahul Munir4 min read729 words

Mediator Pattern: Centralizing Communication Between Components

Backend Development
design pattern
behavioral pattern

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.