The Mediator Pattern is a behavioral design pattern that defines an object that encapsulates the communication between a set of objects, known as colleagues. Instead of allowing colleagues to communicate directly, they communicate through the mediator. This pattern promotes loose coupling between objects, making it easier to modify, extend, and maintain the system. In this article, we will explore the Mediator Pattern in-depth, examining its structure, advantages, and providing practical examples in C#.
Understanding the Mediator Pattern
The Mediator Pattern involves the following key components:
- Mediator: The interface or abstract class that declares the methods for communication between colleagues. It typically includes methods for colleagues to send and receive messages.
- ConcreteMediator: The class that implements the
Mediator
interface and manages the communication between colleagues. It holds references to all colleagues and coordinates their interactions. - Colleague: The interface or abstract class that declares methods for communication with other colleagues. Colleagues are aware of the mediator but do not have direct references to other colleagues.
- ConcreteColleague: The class that implements the
Colleague
interface and communicates with other colleagues through the mediator. It is unaware of other concrete colleagues.
Implementation in C#
Let's delve into a simple example of the Mediator Pattern in C#. Suppose we want to create a chat application where users can send messages to each other. The Mediator Pattern can be applied to manage the communication between users.
// Step 1: Define Mediator interface
public interface IChatMediator
{
void SendMessage(string message, IUser sender);
}
// Step 2: Implement ConcreteMediator
public class ChatMediator : IChatMediator
{
private readonly List<IUser> users;
public ChatMediator()
{
users = new List<IUser>();
}
public void AddUser(IUser user)
{
users.Add(user);
}
public void SendMessage(string message, IUser sender)
{
foreach (var user in users)
{
// Exclude the sender from receiving the message
if (user != sender)
{
user.ReceiveMessage(message);
}
}
}
}
// Step 3: Define Colleague interface
public interface IUser
{
void SendMessage(string message);
void ReceiveMessage(string message);
}
// Step 4: Implement ConcreteColleague
public class ChatUser : IUser
{
private readonly string name;
private readonly IChatMediator mediator;
public ChatUser(string name, IChatMediator mediator)
{
this.name = name;
this.mediator = mediator;
}
public void SendMessage(string message)
{
Console.WriteLine($"{name} sends message: {message}");
mediator.SendMessage(message, this);
}
public void ReceiveMessage(string message)
{
Console.WriteLine($"{name} receives message: {message}");
}
}
// Step 5: Client code
public class Client
{
public void Run()
{
IChatMediator chatMediator = new ChatMediator();
IUser user1 = new ChatUser("User 1", chatMediator);
IUser user2 = new ChatUser("User 2", chatMediator);
IUser user3 = new ChatUser("User 3", chatMediator);
chatMediator.AddUser(user1);
chatMediator.AddUser(user2);
chatMediator.AddUser(user3);
user1.SendMessage("Hello, everyone!");
}
}
In this example, IChatMediator
is the mediator interface that declares the SendMessage
method. ChatMediator
is the concrete mediator class that implements the IChatMediator
interface and manages the communication between users. IUser
is the colleague interface that declares the SendMessage
and ReceiveMessage
methods. ChatUser
is the concrete colleague class that implements the IUser
interface and communicates with other users through the mediator.
Advantages of the Mediator Pattern
1. Decoupling: The Mediator Pattern promotes loose coupling between objects by eliminating direct references between them. This makes it easier to modify and extend the system.
2. Centralized Control: The pattern provides a centralized control point for communication, allowing better coordination and management of interactions.
3. Reusability: Colleagues can be reused in different scenarios as they are unaware of each other's existence. Adding new colleagues is also straightforward.
4. Simplification of Communication: Colleagues communicate through a mediator, simplifying the communication process and avoiding complex interconnections.
Real-world Examples
1. Air Traffic Control System
In an air traffic control system, aircraft communicate with each other through a central air traffic control (ATC) system. The ATC system acts as a mediator, facilitating communication between aircraft to avoid collisions and ensure safe navigation.
// Simplified example in C#
public interface IAirTrafficControl
{
void RegisterAircraft(Aircraft aircraft);
void SendMessage(string message, Aircraft sender);
}
public class Aircraft
{
private readonly string callsign;
private readonly IAirTrafficControl atc;
public Aircraft(string callsign, IAirTrafficControl atc)
{
this.callsign = callsign;
this.atc = atc;
atc.RegisterAircraft(this);
}
public void SendMessage(string message)
{
Console.WriteLine($"{callsign} sends message: {message}");
atc.SendMessage(message, this);
}
public void ReceiveMessage(string message)
{
Console.WriteLine($"{callsign} receives message: {message}");
}
}
In this example, IAirTrafficControl
is the mediator interface that declares the RegisterAircraft
and SendMessage
methods. The Aircraft
class is a colleague that registers with the ATC system and communicates with other aircraft through the mediator.
2. Stock Trading System
In a stock trading system, different stock brokers and traders may need to communicate to execute trades and update market information. The Mediator Pattern can be applied to manage this communication.
// Simplified example in C#
public interface IStockExchange
{
void RegisterParticipant(IParticipant participant);
void BroadcastMessage(string message, IParticipant sender);
}
public interface IParticipant
{
void SendMessage(string message);
void ReceiveMessage(string message);
}
public class StockBroker : IParticipant
{
private readonly string name;
private readonly IStockExchange stockExchange;
public StockBroker(string name, IStockExchange stockExchange)
{
this.name = name;
this.stockExchange = stockExchange;
stockExchange.RegisterParticipant(this);
}
public void SendMessage(string message)
{
Console.WriteLine($"{name} sends message: {message}");
stockExchange.BroadcastMessage(message, this);
}
public void ReceiveMessage(string message)
{
Console.WriteLine($"{name} receives message: {message}");
}
}
In this example, IStockExchange
is the mediator interface that declares the RegisterParticipant
and BroadcastMessage
methods. IParticipant
is the colleague interface that declares the SendMessage
and ReceiveMessage
methods. StockBroker
is a concrete colleague that registers with the stock exchange and communicates with other participants through the mediator.
Conclusion
The Mediator Pattern is a valuable tool for managing communication between objects in a system, promoting loose coupling and flexibility. Through practical examples in C#, we have demonstrated how the Mediator Pattern can be applied to real-world scenarios, providing a blueprint for creating systems that involve the coordination of multiple objects. Understanding and incorporating this pattern into your design practices can contribute to building modular, extensible, and maintainable software architectures, ensuring efficient communication between components in your applications.