Discover the Mediator Pattern – a design pattern that reduces tangled dependencies between objects by introducing a central coordinator to manage all communications.
No table of contents available for this article
Have you ever been in a meeting where everyone talks over each other, and nobody listens? Chaotic, right? Now imagine having a facilitator in the middle – everyone speaks through them, and they relay messages to the right people. That's exactly what the Mediator Pattern does.
The Mediator Pattern belongs to the Behavioral Pattern group. As the name suggests, "Mediator" means "middleman" – this pattern defines an object that encapsulates how a set of objects interact with each other.
The key benefit of Mediator is promoting loose coupling. Instead of letting objects call each other directly, they communicate through the Mediator. This allows you to change how objects interact without modifying the objects themselves.
While Mediator Pattern might seem similar to the Adapter Pattern, they serve completely different purposes. Adapter "translates" between incompatible interfaces, while Mediator acts as a central hub to coordinate communication.
Imagine you're building a dialog for creating and editing customer information. The dialog contains multiple elements: text fields for name, email, phone number; checkboxes for "Married" and "Has Children"; Submit and Cancel buttons...
These components don't work in isolation – they need to interact. For example:
When the user checks "Has Children", a hidden text field appears to enter the number of children.
When "Married" is selected, a "Spouse Name" field appears.
The Submit button needs to validate all fields before allowing form submission.
What happens if you implement this logic directly in each component?
The "Has Children" checkbox must know about the "Number of Children" text field to show/hide it. The Submit button must know about all text fields to validate them. Each component must "know" about many other components – creating a tangled web of dependencies.
The result? Want to reuse the "Has Children" checkbox in another screen? You can't – it's tightly coupled to the "Number of Children" field in the current dialog. The code becomes rigid, hard to maintain, and nearly impossible to reuse.
The Mediator Pattern proposes a different approach: stop all direct communication between components.
Instead of the checkbox calling the text field directly, it sends a notification: "Hey Mediator, I just got checked!". The Mediator receives this and decides: "Ah, the 'Has Children' checkbox is checked, so I need to show the 'Number of Children' field."
This way, each component only knows about the Mediator, not any other component. The checkbox doesn't need to know the text field exists. The Submit button just tells the Mediator: "I was clicked!", and the Mediator handles validation.
Let's examine the Submit button specifically:
Before: The button had to iterate through all form elements, validating each one. It needed to know how many text fields exist, which checkboxes are checked...
After: The button simply sends one notification to the Mediator (in this case, the Dialog). The Dialog handles validation or delegates to components.
For more flexibility, you can create a common interface for all dialog types. This interface declares a notify() method that any component can use. This way, the Submit button works with any dialog implementing this interface – no longer tied to a specific dialog.
The Mediator Pattern consists of these main components:
Component
These are classes containing business logic like Button, TextField, Checkbox... Each component holds a reference to the Mediator, but only knows the Mediator through its interface, not the concrete class. This enables reusing components in other programs – just "plug in" a different mediator.
Mediator Interface
Declares methods for communicating with components. Usually, a single notify() method suffices. Components can pass context (including themselves) to this method, but in a way that doesn't create coupling between sender and receiver.
Concrete Mediator
The class that actually encapsulates relationships between components. Concrete Mediator typically holds references to all components it manages, and sometimes even manages their lifecycle (creation, destruction).
Flow of Operation:
An event occurs on Component A (e.g., user click)
Component A calls mediator.notify(this, "clicked")
Mediator receives the notification, identifies the source and event
Mediator decides whether to trigger Component B, C, etc.
Mediator calls appropriate methods on Component B, C
Components never communicate directly with each other – everything goes through the Mediator.
Single Responsibility Principle (SRP): All communication coordination logic is centralized in the Mediator. Want to understand how components interact? Just read the Mediator's code.
Open/Closed Principle (OCP): Need to change communication behavior? Create a new Mediator without modifying existing components.
Reduced Coupling: Components no longer depend on each other. Each component can be developed, tested, and maintained independently.
Easy Reusability: Want to use a Button in another dialog? Just connect it to a different Mediator.
Simplified Communication: Transforms complex many-to-many relationships into simpler one-to-many relationships. Instead of N components tangled together, all communicate with just 1 Mediator.
Centralized Management: Easy to track and debug communication flow in the system.
God Object Risk: Over time, the Mediator tends to grow as you add more components and coordination logic. Without care, it becomes a "God Object" – a class that knows too much and does too much.
To mitigate this, you can split the Mediator into sub-mediators for related component groups.
When classes are too tightly coupled: You want to modify one class but end up changing a chain of other classes? That's a sign you need Mediator.
When components aren't reusable: A button can't be used on another screen because it "knows" too much about surrounding components? Mediator will help.
When creating too many subclasses: You're creating ButtonForDialogA, ButtonForDialogB, ButtonForDialogC just because each dialog has different logic? Use Mediator instead of subclasses.
Typical Use Cases:
Chat systems: Mediator (Chat Room) coordinates messages between Users
GUI frameworks: Dialog acts as mediator for UI controls
Flight booking systems: Mediator connects flight search, payment, and confirmation email modules
Air Traffic Control: The Tower coordinates communication between aircraft (a classic example!)
Here's a Mediator Pattern implementation:
csharp
// Mediator Interface - defines communication method
public interface IMediator
{
void Notify(object sender, string ev);
}
// Concrete Mediator - coordinates communication between components
class ConcreteMediator : IMediator
{
private Component1 _component1;
private Component2 _component2;
public ConcreteMediator(Component1 component1, Component2 component2)
{
this._component1 = component1;
this._component1.SetMediator(this);
this._component2 = component2;
this._component2.SetMediator(this);
}
public void Notify(object sender, string ev)
{
if (ev == "A")
{
Console.WriteLine("Mediator reacts to A and triggers following operations:");
this._component2.DoC();
}
if (ev == "D")
{
Console.WriteLine("Mediator reacts to D and triggers following operations:");
this._component1.DoB();
this._component2.DoC();
}
}
}
// Base Component - stores Mediator reference
class BaseComponent
{
protected IMediator _mediator;
public BaseComponent(IMediator mediator = null)
{
this._mediator = mediator;
}
public void SetMediator(IMediator mediator)
{
this._mediator = mediator;
}
}
// Concrete Components
class Component1 : BaseComponent
{
public void DoA()
{
Console.WriteLine("Component 1 does A.");
this._mediator.Notify(this, "A");
}
public void DoB()
{
Console.WriteLine("Component 1 does B.");
this._mediator.Notify(this, "B");
}
}
class Component2 : BaseComponent
{
public void DoC()
{
Console.WriteLine("Component 2 does C.");
this._mediator.Notify(this, "C");
}
public void DoD()
{
Console.WriteLine("Component 2 does D.");
this._mediator.Notify(this, "D");
}
}
// Client code
class Program
{
static void Main(string[] args)
{
Component1 component1 = new Component1();
Component2 component2 = new Component2();
new ConcreteMediator(component1, component2);
Console.WriteLine("Client triggers operation A.");
component1.DoA();
Console.WriteLine();
Console.WriteLine("Client triggers operation D.");
component2.DoD();
}
}
```
**Output:**
```
Client triggers operation A.
Component 1 does A.
Mediator reacts to A and triggers following operations:
Component 2 does C.
Client triggers operation D.
Component 2 does D.
Mediator reacts to D and triggers following operations:
Component 1 does B.
Component 2 does C.In this example, when Component1 performs action A, it notifies the Mediator. The Mediator then decides to trigger Component2 to perform C. The components know nothing about each other – everything flows through the Mediator.
Chain of Responsibility, Command, Mediator, and Observer all address the sender-receiver connection problem, but in different ways:
Chain of Responsibility: Passes requests sequentially through a handler chain until one handles it.
Command: Creates unidirectional connections between sender and receiver, encapsulating requests as objects.
Mediator: Eliminates direct connections, forcing communication through an intermediary.
Observer: Allows receivers to actively subscribe/unsubscribe to notifications.
Facade vs Mediator:
Both organize collaboration among multiple classes, but:
Facade: Provides a simplified interface to a subsystem. Objects within the subsystem can still communicate directly.
Mediator: Intermediates all communication. Components only know the Mediator and don't communicate directly with each other.
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
References
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns