The Mediator pattern defines an object which encapsulates how a set of objects interact with each other.
You can think of a Mediator object as a kind of traffic-coordinator; it directs traffic to appropriate parties based on its own state or outside values. Further, Mediator promotes loose coupling (a good thing!) by keeping objects from referring to each other explicitly.
NOTE: This post is part of a series demonstrating software design patterns using C# and .NET. The patterns are taken from the book Design Patterns by the Gang of Four. Check out the other posts in this series!
The Rundown
- Type: Behavioral
- Useful? 2/5 (Uncommon)
- Good For: Defining how objects interact with each other.
- Example Code: On GitHub
The Participants
- The Mediator defines an interface for communicating with Collegue objects.
- The Colleague classes each know what Mediator is responsible for them and communicates with said Mediator whenever it would have otherwise communicated directly with another Colleague.
- The ConcreteMediator classes implement behavior to coordinate Colleague objects. Each ConcreteMediator knows what its constituent Colleague classes are.
A Delicious Example
To demo the Mediator pattern, let's consider the snack bars in your local movie theatre.
Movie theatres, relative to other kinds of buildings, tend to take up a lot of ground space. A particular cinema that's not too far from me has 25 screens spread out over three different "sections" of the theatre. Each of these sections, being far enough apart from each other, has their own snack bar, from which we gluttonous patrons can order salty snacks and sugary drinks to our increasingly-stressed heart's content.
But selling concessions to hungry movie-goers requires supplies, and sometimes the different snack bars might run out of them. Let's imagine a system in which the different concession stands can talk to each other, communicating what supplies they need and who might have them (in short, a chat system for movie snack bars). We can model this system using the Mediator pattern.
First, we'll need our Mediator interface, which defines a method by which the snack bars can talk to each other:
/// <summary>
/// The Mediator interface, which
/// defines a send message method
/// which the concrete mediators must implement.
/// </summary>
interface Mediator
{
void SendMessage(string message, ConcessionStand concessionStand);
}
We also need an abstract class to represent the Colleagues that will be talking to one another:
/// <summary>
/// The Colleague abstract class, representing
/// an entity involved in the conversation
/// which should receive messages.
/// </summary>
abstract class ConcessionStand
{
protected Mediator mediator;
public ConcessionStand(Mediator mediator)
{
this.mediator = mediator;
}
}
Now let's implement the different Colleagues. In this case, we'll pretend our movie theatre has two snack bars: one in the northern part of the theatre and one in the southern part.
/// <summary>
/// A Concrete Colleague class
/// </summary>
class NorthConcessionStand : ConcessionStand
{
// Constructor
public NorthConcessionStand(Mediator mediator) : base(mediator) { }
public void Send(string message)
{
Console.WriteLine("North Concession Stand sends message: " + message);
mediator.SendMessage(message, this);
}
public void Notify(string message)
{
Console.WriteLine("North Concession Stand gets message: " + message);
}
}
/// <summary>
/// A Concrete Colleague class
/// </summary>
class SouthConcessionStand : ConcessionStand
{
public SouthConcessionStand(Mediator mediator) : base(mediator) { }
public void Send(string message)
{
Console.WriteLine("South Concession Stand sends message: " + message);
mediator.SendMessage(message, this);
}
public void Notify(string message)
{
Console.WriteLine("South Concession Stand gets message: " + message);
}
}
Note that each Colleague must be aware of the Mediator that is mediating the colleague's messages.
Finally, we can implement the ConcreteMediator class, which will keep a reference to each Colleague and manage communication between them.
/// <summary>
/// The Concrete Mediator class, which implement the send message method and keep track of all participants in the conversation.
/// </summary>
class ConcessionsMediator : Mediator
{
private NorthConcessionStand _northConcessions;
private SouthConcessionStand _southConcessions;
public NorthConcessionStand NorthConcessions
{
set { _northConcessions = value; }
}
public SouthConcessionStand SouthConcessions
{
set { _southConcessions = value; }
}
public void SendMessage(string message, ConcessionStand colleague)
{
if (colleague == _northConcessions)
{
_southConcessions.Notify(message);
}
else
{
_northConcessions.Notify(message);
}
}
}
In our Main()
, we can use our newly-written Mediator to simulate a chat conversation between the two snack bars. Suppose that one of the snack bars has run out of popcorn, and needs to know if the other has extra that they're not using:
static void Main(string[] args)
{
ConcessionsMediator mediator = new ConcessionsMediator();
NorthConcessionStand leftKitchen = new NorthConcessionStand(mediator);
SouthConcessionStand rightKitchen = new SouthConcessionStand(mediator);
mediator.NorthConcessions = leftKitchen;
mediator.SouthConcessions = rightKitchen;
leftKitchen.Send("Can you send some popcorn?");
rightKitchen.Send("Sure thing, Kenny's on his way.");
rightKitchen.Send("Do you have any extra hot dogs? We've had a rush on them over here.");
leftKitchen.Send("Just a couple, we'll send Kenny back with them.");
Console.ReadKey();
}
If we run this app, we'll see a conversation between the two concession stands (send/receive messages added to clearly show who sent what):
Will I Ever Use This Pattern?
To be honest, probably not. It's only useful in specific scenarios (e.g. chat systems and the like) and may not be terribly applicable to other types of projects. That said, I'm always open to ideas for using these patterns, so if you've used it in a real-world scenario and want to share with us, please do so in the comments!
UPDATE (Oct 2020): Turns out I was wrong about this. The Mediator pattern has gotten a LOT more useful in recent years, with packages like MediatR growing steadily in popularity. You are now more likely to use this pattern than many of the other patterns in this series.
Summary
The Mediator pattern encapsulates an object which represents how other objects communicate with one another. By doing so, it enables the Mediator to "stand between" communicating objects and control their communications.
Happy Coding! And don't forget the popcorn!