Skip to main content

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
tip

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​

MediatorObserver
CommunicationTwo-way (colleague ↔ mediator)One-way (subject β†’ observer)
Who knows whomMediator knows all colleaguesSubject doesn't know observers
ComplexityCentralized in mediatorDistributed across observers
Use whenMulti-way coordination neededSimple 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.