Summary: in this tutorial, you’ll learn about the C# Mediator pattern to encapsulate object interaction with loose coupling.
Introduction to the C# Mediator pattern
The Mediator pattern defines an object (mediator) that encapsulates the interactions of other objects. The Mediator pattern promotes loose coupling by preventing objects from referring to each other explicitly and allows you to manage their interaction independently.
In the Mediator pattern, a mediator object serves as an intermediary between objects that need to interact with each other.
So instead of allowing objects to interact directly with each other, they communicate via the mediator object. The Mediator object can also provide a centralized point of control to coordinate the interactions between the objects:
The Mediator pattern promotes loose coupling between objects because they don’t need to know about each other’s details and they only have the information about the mediator object. This allows the system easier to maintain and modify because changes to one object won’t affect the others.
The following UML diagram illustrates the Mediator pattern:
In this diagram:
Mediator
defines an interface for communicating withColleague
objects.ConcreteMediator
implements cooperative behavior by coordinatingColleague
objects. TheConcreteMeditor
maintains a list of colleagues.Colleague
is an interface or abstract class that defines objects that need to interact with each other.ConcreteColleague
is a concrete class of theColleague
class.
The following shows an implementation of the Mediator pattern in C#:
namespace MediatorPattern;
public abstract class Mediator
{
public abstract void Send(string message, Colleague colleague);
}
public abstract class Colleague
{
private Mediator _mediator;
public Colleague(Mediator mediator)
{
_mediator = mediator;
}
public virtual void Send(string message)
{
_mediator.Send(message, this);
}
public abstract void Receive(string message);
}
public class Colleague1 : Colleague
{
public Colleague1(Mediator mediator) : base(mediator)
{
}
public override void Receive(string message) => Console.WriteLine($"Colleague1 received {message}");
}
public class Colleague2 : Colleague
{
public Colleague2(Mediator mediator) : base(mediator)
{
}
public override void Receive(string message) => Console.WriteLine($"Colleague2 received {message}");
}
public class ConcreteMediator : Mediator
{
public Colleague1 Colleague1;
public Colleague2 Colleague2;
public override void Send(string message, Colleague colleague)
{
if (colleague == Colleague1)
{
Colleague2.Receive($"{message} from {nameof(Colleague1)}");
}
else if (colleague == Colleague2)
{
Colleague1.Receive($"{message} from {nameof(Colleague2)}");
}
}
}
public class Program
{
public static void Main(string[] args)
{
var mediator = new ConcreteMediator();
var c1 = new Colleague1(mediator);
var c2 = new Colleague2(mediator);
mediator.Colleague1 = c1;
mediator.Colleague2 = c2;
c1.Send("Hello");
c2.Send("Hi");
c1.Send("Bye");
c2.Send("Bye bye");
}
}
Code language: C# (cs)
How it works.
First, define a Mediator
abstract class that has the Send()
method for sending a message to a Colleague:
public abstract class Mediator
{
public abstract void Send(string message, Colleague colleague);
}
Code language: C# (cs)
Next, define the Colleague
class abstract class that serves as a base class for other colleague objects:
public abstract class Colleague
{
private Mediator _mediator;
public Colleague(Mediator mediator)
{
_mediator = mediator;
}
public virtual void Send(string message)
{
_mediator.Send(message, this);
}
public abstract void Receive(string message);
}
Code language: C# (cs)
The Colleague
object knows the Mediator
object. The Send()
method uses the mediator object to send a message.
Then, define two Colleague concrete classes that inherit from the Colleague
class:
public class Colleague1 : Colleague
{
public Colleague1(Mediator mediator) : base(mediator)
{
}
public override void Receive(string message) => Console.WriteLine($"Colleague1 received {message}");
}
public class Colleague2 : Colleague
{
public Colleague2(Mediator mediator) : base(mediator)
{
}
public override void Receive(string message) => Console.WriteLine($"Colleague2 received {message}");
}
Code language: C# (cs)
The Colleague1
and Colleague2
classes implement the Receive()
method that shows the message that they receive to the console.
After that, define the
class that extends the Mediator class. The ConcreteMediator
knows the ConcreteMediator
Colleague1
and Colleague2
objects. Its Send()
method coordinates the communication between these objects:
public class ConcreteMediator : Mediator
{
public Colleague1 Colleague1;
public Colleague2 Colleague2;
public override void Send(string message, Colleague colleague)
{
if (colleague == Colleague1)
{
Colleague2.Receive($"{message} from {nameof(Colleague1)}");
}
else if (colleague == Colleague2)
{
Colleague1.Receive($"{message} from {nameof(Colleague2)}");
}
}
}
Code language: C# (cs)
Finally, create the ConcreteMediator
, Colleague1
, and Colleague2
objects and send messages from Colleague1
and Colleague2
:
public class Program
{
public static void Main(string[] args)
{
var mediator = new ConcreteMediator();
var c1 = new Colleague1(mediator);
var c2 = new Colleague2(mediator);
mediator.Colleague1 = c1;
mediator.Colleague2 = c2;
c1.Send("Hello");
c2.Send("Hi");
c1.Send("Bye");
c2.Send("Bye bye");
}
}
Code language: C# (cs)
C# Mediator pattern variant
The following provides a variant of the Mediator pattern but in a more elegant way:
namespace MediatorPattern;
public abstract class Mediator
{
public abstract void Send(string message, Colleague colleague);
}
public abstract class Colleague
{
public Mediator? Mediator { set; get;}
public virtual void Send(string message) => Mediator?.Send(message, this);
public abstract void Receive(string message);
}
public class Colleague1 : Colleague
{
public override void Receive(string message) => Console.WriteLine($"Colleague1 received {message}");
}
public class Colleague2 : Colleague
{
public override void Receive(string message) => Console.WriteLine($"Colleague2 received {message}");
}
public class ConcreteMediator : Mediator
{
private readonly List<Colleague> _colleagues = new();
public void Register(Colleague colleague)
{
colleague.Mediator = this;
_colleagues.Add(colleague);
}
public override void Send(string message, Colleague receiver)
{
// Send a message from the list of colleagues,
// which are not receiver, to the receiver
_colleagues
.Where(c => c != receiver)
.ToList()
.ForEach(c => c.Receive(message));
}
}
public class Program
{
public static void Run(string[] args)
{
var mediator = new ConcreteMediator();
var c1 = new Colleague1();
var c2 = new Colleague2();
mediator.Register(c1);
mediator.Register(c2);
c1.Send("Hello");
c2.Send("Hi");
c1.Send("Bye");
c2.Send("Bye bye");
}
}
Code language: C# (cs)
In this implementation:
- The
Colleague
class has aMediator
property so that you can set the mediator object. - The
ConcreteMediator
class maintains a list ofColleague
objects instead of referring toColleague1
andColleague2
directly. - The
Register()
method ofConcreteMediator
class adds aColleague
to aColleague
list. - The
Send()
method sends a message from a list of colleagues except the receiver (colleague) to the receiver (colleague)
Mediator pattern in .NET
The MediatR is a package that is a simple mediator implementation in . NET.
Summary
- Use the C# Mediator pattern to encapsulate object interaction with loose coupling.