Mediator Pattern
Definitionβ
The Mediator defines an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, letting the mediator coordinate their interactions.
Coffee Shop Exampleβ
During a busy shift, the barista, cashier, kitchen, and delivery driver all need to communicate. Instead of each component knowing about every other component, they communicate through a CoffeeShopMediator β a central coordination hub.
Structureβ
Mediator Interfaceβ
public interface ICoffeeShopMediator
{
void Notify(string sender, string @event, string data);
}
Components (Colleagues)β
public class CashierComponent
{
private readonly ICoffeeShopMediator _mediator;
public CashierComponent(ICoffeeShopMediator mediator) => _mediator = mediator;
public void TakeOrder(string customer, string coffee)
{
Console.WriteLine($" [Cashier] {customer} orders {coffee}");
_mediator.Notify("Cashier", "OrderPlaced", $"{customer}:{coffee}");
}
public void ProcessPayment(string customer, decimal amount) =>
Console.WriteLine($" [Cashier] Payment ${amount:F2} received from {customer}");
}
public class BaristaComponent
{
private readonly ICoffeeShopMediator _mediator;
public BaristaComponent(ICoffeeShopMediator mediator) => _mediator = mediator;
public void Brew(string coffee)
{
Console.WriteLine($" [Barista] Brewing {coffee}...");
_mediator.Notify("Barista", "CoffeeReady", coffee);
}
}
public class KitchenComponent
{
private readonly ICoffeeShopMediator _mediator;
public KitchenComponent(ICoffeeShopMediator mediator) => _mediator = mediator;
public void PrepareFood(string item)
{
Console.WriteLine($" [Kitchen] Preparing {item}...");
_mediator.Notify("Kitchen", "FoodReady", item);
}
}
public class DeliveryComponent
{
public void Deliver(string order) =>
Console.WriteLine($" [Delivery] Delivering: {order}");
}
Concrete Mediatorβ
public class CoffeeShopMediator : ICoffeeShopMediator
{
private CashierComponent? _cashier;
private BaristaComponent? _barista;
private KitchenComponent? _kitchen;
private DeliveryComponent? _delivery;
public void Register(CashierComponent c) => _cashier = c;
public void Register(BaristaComponent b) => _barista = b;
public void Register(KitchenComponent k) => _kitchen = k;
public void Register(DeliveryComponent d) => _delivery = d;
public void Notify(string sender, string @event, string data)
{
switch (@event)
{
case "OrderPlaced":
var parts = data.Split(':');
var customer = parts[0];
var coffee = parts[1];
_cashier?.ProcessPayment(customer, 4.50m);
_barista?.Brew(coffee);
break;
case "CoffeeReady":
Console.WriteLine($" [Mediator] {data} is ready!");
// If it's a combo, tell kitchen to prepare food
if (data == "Combo")
_kitchen?.PrepareFood("Sandwich");
else
_delivery?.Deliver(data);
break;
case "FoodReady":
Console.WriteLine($" [Mediator] {data} is ready!");
_delivery?.Deliver($"Combo with {data}");
break;
}
}
}
Clientβ
var mediator = new CoffeeShopMediator();
var cashier = new CashierComponent(mediator);
var barista = new BaristaComponent(mediator);
var kitchen = new KitchenComponent(mediator);
var delivery = new DeliveryComponent();
mediator.Register(cashier);
mediator.Register(barista);
mediator.Register(kitchen);
mediator.Register(delivery);
// Simple coffee order
Console.WriteLine("--- Simple Order ---");
cashier.TakeOrder("Alice", "Latte");
// Combo order
Console.WriteLine("\n--- Combo Order ---");
cashier.TakeOrder("Bob", "Combo");
// Output:
// --- Simple Order ---
// [Cashier] Alice orders Latte
// [Cashier] Payment $4.50 received from Alice
// [Barista] Brewing Latte...
// [Mediator] Latte is ready!
// [Delivery] Delivering: Latte
//
// --- Combo Order ---
// [Cashier] Bob orders Combo
// [Cashier] Payment $4.50 received from Bob
// [Barista] Brewing Combo...
// [Mediator] Combo is ready!
// [Kitchen] Preparing Sandwich...
// [Mediator] Sandwich is ready!
// [Delivery] Delivering: Combo with Sandwich
No component talks directly to another. The CashierComponent doesn't know about BaristaComponent. The mediator handles all coordination. Adding a new component (e.g., loyalty tracker) only requires changes to the mediator.
Mediator vs Observerβ
| Mediator | Observer | |
|---|---|---|
| Communication | Two-way (colleague β mediator) | One-way (subject β observer) |
| Who knows whom | Mediator knows all colleagues | Subject doesn't know observers |
| Complexity | Centralized in mediator | Distributed across observers |
| Use when | Multi-way coordination needed | Simple one-to-many notification |
.NET Real-World Usageβ
- MediatR library β the most popular mediator implementation in .NET
- UI Dialog boxes β form controls communicate through a form mediator
- Air traffic control β planes communicate through the tower, not directly
- Chat rooms β users send messages through the server, not peer-to-peer
When to Useβ
- A set of objects communicate in complex but well-defined ways
- You want to avoid many-to-many relationships between components
- Reusing a component is difficult because it refers to many other components
When NOT to Useβ
- Only two components interact β direct communication is simpler
- The mediator becomes a god object β split responsibilities
- Interaction is simple β don't add unnecessary complexity
Key Takeawaysβ
- Mediator centralizes communication between colleagues
- Components only know the mediator β loose coupling between colleagues
- Changes to interaction logic live in one place (the mediator)
- The mediator can become complex β consider splitting into multiple mediators
Interview Questionsβ
Q: How does MediatR implement the Mediator pattern?
MediatR provides IMediator with Send() (command/query) and Publish() (notification). Handlers register via DI. The mediator routes messages to the correct handler without the sender knowing who handles it.
Q: What's the difference between Mediator and Facade? Facade provides a simplified interface to a subsystem β one-way. Mediator provides bidirectional communication between colleagues. Facade abstracts complexity; Mediator decouples communication.
Q: Does Mediator violate SRP? The mediator can grow large if coordination logic is complex. Split it by domain (order mediator, inventory mediator) or use CQRS to separate command and query mediation.
Related Topicsβ
- Observer β one-way notification, simpler
- Facade β simplified interface, one-way
- Chain of Responsibility β request flows through handlers
- SOLID β SRP & DIP β mediator centralizes coordination