You’re building a dialog box with multiple UI components: buttons, text fields, checkboxes. When one component changes, others need to update. Without careful design, every component would need references to every other component, creating a web of dependencies. The Mediator pattern centralizes communication, reducing chaotic dependencies between objects.
Problem
Consider a chat application where users can send messages:
public class User
{
public string Name { get; set; }
public void SendMessage(string message, User recipient)
{
Console.WriteLine($"{Name} to {recipient.Name}: {message}");
recipient.ReceiveMessage(message, this);
}
public void ReceiveMessage(string message, User sender)
{
Console.WriteLine($"{Name} received from {sender.Name}: {message}");
}
}
This works for two users, but what about group chats? Each user would need references to all other users:
public class User
{
public string Name { get; set; }
public List<User> Contacts { get; set; } = new();
public void SendToAll(string message)
{
foreach (var contact in Contacts)
{
contact.ReceiveMessage(message, this);
}
}
}
This creates tight coupling. Adding features like message history, user status, or rooms becomes difficult.
Solution
The Mediator pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by keeping objects from referring to each other explicitly.
Definition: The Mediator pattern lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.
Instead of objects communicating directly, they communicate through a mediator, which coordinates the interaction.
Pseudocode
Colleague1, Colleague2, Colleague3
↓ all communicate through
Mediator (IChatMediator)
↓ coordinates
Communication between colleagues
Examples
Here’s a complete, runnable C# implementation:
using System;
using System.Collections.Generic;
// Mediator interface
public interface IChatMediator
{
void SendMessage(string message, User user);
void AddUser(User user);
}
// Concrete mediator
public class ChatMediator : IChatMediator
{
private List<User> _users = new List<User>();
public void AddUser(User user)
{
_users.Add(user);
}
public void SendMessage(string message, User sender)
{
foreach (var user in _users)
{
if (user != sender)
{
user.ReceiveMessage(message, sender.Name);
}
}
}
}
// Colleague
public class User
{
private IChatMediator _mediator;
public string Name { get; set; }
public User(string name, IChatMediator mediator)
{
Name = name;
_mediator = mediator;
}
public void Send(string message)
{
Console.WriteLine($"{Name} sends: {message}");
_mediator.SendMessage(message, this);
}
public void ReceiveMessage(string message, string senderName)
{
Console.WriteLine($"{Name} received from {senderName}: {message}");
}
}
// Usage
class Program
{
static void Main()
{
var mediator = new ChatMediator();
var alice = new User("Alice", mediator);
var bob = new User("Bob", mediator);
var charlie = new User("Charlie", mediator);
mediator.AddUser(alice);
mediator.AddUser(bob);
mediator.AddUser(charlie);
alice.Send("Hello everyone!");
bob.Send("Hi Alice!");
}
}
Output:
Alice sends: Hello everyone!
Bob received from Alice: Hello everyone!
Charlie received from Alice: Hello everyone!
Bob sends: Hi Alice!
Alice received from Bob: Hi Alice!
Charlie received from Bob: Hi Alice!
Real-World Example: Air Traffic Control
// Mediator
public interface IAirTrafficControl
{
void RegisterAircraft(Aircraft aircraft);
void RequestLanding(Aircraft aircraft);
void NotifyLanding(Aircraft aircraft);
}
public class AirTrafficControl : IAirTrafficControl
{
private List<Aircraft> _aircrafts = new List<Aircraft>();
private Aircraft _landingAircraft = null;
public void RegisterAircraft(Aircraft aircraft)
{
_aircrafts.Add(aircraft);
}
public void RequestLanding(Aircraft aircraft)
{
if (_landingAircraft == null)
{
_landingAircraft = aircraft;
Console.WriteLine($"{aircraft.Name} cleared for landing");
}
else
{
Console.WriteLine($"{aircraft.Name} must wait. {_landingAircraft.Name} is landing");
}
}
public void NotifyLanding(Aircraft aircraft)
{
if (_landingAircraft == aircraft)
{
_landingAircraft = null;
Console.WriteLine($"{aircraft.Name} has landed");
}
}
}
// Colleague
public class Aircraft
{
private IAirTrafficControl _atc;
public string Name { get; set; }
public Aircraft(string name, IAirTrafficControl atc)
{
Name = name;
_atc = atc;
_atc.RegisterAircraft(this);
}
public void RequestLanding()
{
_atc.RequestLanding(this);
}
public void Land()
{
_atc.NotifyLanding(this);
}
}
// Usage
var atc = new AirTrafficControl();
var flight1 = new Aircraft("Flight 101", atc);
var flight2 = new Aircraft("Flight 202", atc);
flight1.RequestLanding();
flight2.RequestLanding();
flight1.Land();
flight2.RequestLanding();
UI Component Example
// Mediator
public interface IDialogMediator
{
void Notify(Component sender, string eventName);
}
public class LoginDialogMediator : IDialogMediator
{
public TextBox UsernameBox { get; set; }
public TextBox PasswordBox { get; set; }
public CheckBox RememberCheckbox { get; set; }
public Button LoginButton { get; set; }
public void Notify(Component sender, string eventName)
{
if (sender == UsernameBox && eventName == "TextChanged")
{
ValidateForm();
}
else if (sender == PasswordBox && eventName == "TextChanged")
{
ValidateForm();
}
else if (sender == LoginButton && eventName == "Click")
{
AttemptLogin();
}
}
private void ValidateForm()
{
bool isValid = !string.IsNullOrEmpty(UsernameBox.Text) &&
!string.IsNullOrEmpty(PasswordBox.Text);
LoginButton.Enabled = isValid;
}
private void AttemptLogin()
{
Console.WriteLine($"Logging in: {UsernameBox.Text}");
}
}
// Component base
public abstract class Component
{
protected IDialogMediator _mediator;
public Component(IDialogMediator mediator)
{
_mediator = mediator;
}
}
public class TextBox : Component
{
private string _text = "";
public string Text
{
get => _text;
set
{
_text = value;
_mediator?.Notify(this, "TextChanged");
}
}
public TextBox(IDialogMediator mediator) : base(mediator) { }
}
public class Button : Component
{
private bool _enabled = false;
public bool Enabled
{
get => _enabled;
set
{
_enabled = value;
Console.WriteLine($"Button enabled: {value}");
}
}
public Button(IDialogMediator mediator) : base(mediator) { }
public void Click()
{
if (_enabled)
{
_mediator?.Notify(this, "Click");
}
}
}
public class CheckBox : Component
{
public bool Checked { get; set; }
public CheckBox(IDialogMediator mediator) : base(mediator) { }
}
Applications
Enterprise Use Cases:
- UI Frameworks: Coordinating interactions between UI components
- Chat Applications: Managing message routing between users
- Workflow Systems: Coordinating steps in business processes
- Event Systems: Centralizing event handling
- Game Development: Managing interactions between game objects
- Trading Systems: Coordinating buy/sell orders
- Air Traffic Control: Managing aircraft communications
- Form Validation: Coordinating validation between form fields
Benefits:
- Reduces coupling between colleagues
- Centralizes control logic
- Makes it easier to understand how objects interact
- Simplifies maintenance
- Follows Single Responsibility Principle—mediator handles coordination
When to Use:
- A set of objects communicate in well-defined but complex ways
- Reusing objects is difficult because they refer to many other objects
- Behavior distributed between classes should be customizable without subclassing
- You want to centralize communication logic
Mediator vs Observer:
- Mediator: Colleagues communicate through mediator (many-to-many)
- Observer: Subject notifies observers directly (one-to-many)
Key Insight: The Mediator pattern centralizes communication between objects. Instead of objects communicating directly (which creates tight coupling), they communicate through a mediator. This reduces dependencies and makes the system easier to understand and maintain.
