Mediator Design Pattern in C# with Example

1. Definition

The Mediator Design 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, and it lets you vary their interaction independently.

2. Problem Statement

Imagine a situation where several components (like buttons, text boxes, and dropdowns) in a GUI application interact with each other directly. If you wanted to change the behavior of one component, it might involve changing many other components. This leads to a system where components are tightly coupled, making it hard to maintain and extend.

3. Solution

The Mediator pattern suggests that you should cease all direct communication between the components which you want to make independent of each other. Instead, these components must collaborate indirectly, by calling a special mediator object that redirects the calls to appropriate components. As a result, the components depend only on a single mediator class instead of being coupled to dozens of their colleagues.

4. Real-World Use Cases

1. Chat rooms where users send messages to a central hub, and then the hub sends those messages to other users.

2. Air traffic control towers coordinating flights.

3. GUI libraries where interactions between multiple UI components are managed.

5. Implementation Steps

1. Define a mediator interface that will communicate with colleague objects.

2. Create a concrete mediator class that implements the mediator interface.

3. Colleague classes should have a reference to the mediator object. Instead of communicating directly with each other, they will communicate through the mediator.

6. Implementation in C#

// Mediator interface
public interface IMediator
{
    void Notify(object sender, string ev);
}

// Concrete Mediator
public class ConcreteMediator : IMediator
{
    public Colleague1 Colleague1 { get; set; }
    public Colleague2 Colleague2 { get; set; }

    public void Notify(object sender, string ev)
    {
        if (ev == "A")
        {
            Console.WriteLine("Mediator reacts on A and triggers following operations:");
            this.Colleague2.DoC();
        }
        if (ev == "D")
        {
            Console.WriteLine("Mediator reacts on D and triggers following operations:");
            this.Colleague1.DoB();
            this.Colleague2.DoC();
        }
    }
}

// Base Colleague
public abstract class BaseColleague
{
    protected IMediator _mediator;

    public BaseColleague(IMediator mediator)
    {
        this._mediator = mediator;
    }
}

// Concrete Colleague 1
public class Colleague1 : BaseColleague
{
    public Colleague1(IMediator mediator) : base(mediator) { }

    public void DoA()
    {
        Console.WriteLine("Colleague1 does A.");
        _mediator.Notify(this, "A");
    }

    public void DoB()
    {
        Console.WriteLine("Colleague1 does B.");
        _mediator.Notify(this, "B");
    }
}

// Concrete Colleague 2
public class Colleague2 : BaseColleague
{
    public Colleague2(IMediator mediator) : base(mediator) { }

    public void DoC()
    {
        Console.WriteLine("Colleague2 does C.");
    }

    public void DoD()
    {
        Console.WriteLine("Colleague2 does D.");
        _mediator.Notify(this, "D");
    }
}

public class Program
{
    public static void Main()
    {
        // Client code
        ConcreteMediator mediator = new ConcreteMediator();
        Colleague1 colleague1 = new Colleague1(mediator);
        Colleague2 colleague2 = new Colleague2(mediator);

        mediator.Colleague1 = colleague1;
        mediator.Colleague2 = colleague2;

        Console.WriteLine("Client triggers operation A.");
        colleague1.DoA();

        Console.WriteLine();

        Console.WriteLine("Client triggers operation D.");
        colleague2.DoD();
    }
}

Output:

Client triggers operation A.
Colleague1 does A.
Mediator reacts on A and triggers following operations:
Colleague2 does C.

Client triggers operation D.
Colleague2 does D.
Mediator reacts on D and triggers following operations:
Colleague1 does B.
Colleague2 does C.

Explanation:

In the provided example, Colleague1 and Colleague2 represent components that would like to interact with each other. Instead of doing so directly, they use the ConcreteMediator. When they want to send messages or notifications, they do so through the mediator, which then decides what actions should be taken.

7. When to use?

1. Use the Mediator pattern when you want to reduce the chaotic dependencies between multiple classes or objects by centralizing external communications.

2. When you find yourself creating tons of subclass relationships to reuse some common behavior, consider using the Mediator. Instead of subclassing, you place the shared code in the mediator.


Comments