Mediator Pattern

Hello, coding enthusiasts! Welcome back to .Net Core Central. In today’s blog post, we’re delving into the intriguing realm of the Mediator design pattern.

This behavioral design pattern serves as a powerful tool for managing interactions between a set of objects, promoting loose coupling and independent variation of their interactions.

Understanding the Mediator Pattern

The essence of the Mediator pattern lies in having an object that encapsulates the communication between a group of objects. By doing so, it eliminates direct references between these objects, fostering loose coupling.

Similar to other behavioral patterns, the primary goal here is to establish flexibility in the interactions between multiple objects.

Real-world Example: Call Center-based Cab Booking System

To illustrate the Mediator pattern, let’s consider a real-world scenario – a call center-based cab booking system. In the era before ride-sharing apps like Uber, we used to book cabs through call centers.

Each cab would register itself with the call center, and when a passenger called to book a cab, the call center managed the communication between the passenger and available cabs.

Implementation of Mediator Pattern in .NET Core

For our demonstration, we’ll create three classes: CallCenter, Cab, and Passenger. The CallCenter class is responsible for registering cabs and handling cab bookings. Cabs and passengers, in turn, interact with the call center instead of directly communicating with each other.

Cab Class

The Cab class represents a cab, with properties such as name, location, and availability. Cabs register themselves with the call center and can be assigned to passengers within a certain radius.

namespace MediatorPattern.Demo;

internal interface ICab
{
    string Name { get; }
    int CurrentLocation { get; }
    bool IsFree { get; }

    void Assign(string name, string address);
}

internal class Cab : ICab
{
    private readonly string _name;
    private readonly int _location;
    private readonly bool _free;

    public Cab(string name, int location, bool free)
    {
        _name = name;
        _location = location;
        _free = free;
    }

    public string Name => _name;

    public int CurrentLocation => _location;

    public bool IsFree => _free;

    public void Assign(string name, string address) =>
        Console.WriteLine($"Cab {Name}, assigned to passenger: {name}, {address}");
}

CallCenter Class

The CallCenter class maintains a dictionary of registered cabs and handles the booking process. It checks for available cabs within a certain radius of the passenger’s location and assigns the cab accordingly.

namespace MediatorPattern.Demo;

internal interface ICabCallCenter
{
    void Register(ICab cab);
    void BookCab(IPassenger passenger);
}

internal class CabCallCenter : ICabCallCenter
{
    private readonly Dictionary<string, ICab> cabs
        = new Dictionary<string, ICab>();
    public void BookCab(IPassenger passenger)
    {
        foreach (var cab in cabs.Values.Where(c => c.IsFree))
        {
            if(IsWithin5MileRadius(cab.CurrentLocation, passenger.Location))
            {
                cab.Assign(passenger.Name, passenger.Address);
                passenger.Acknowledge(cab.Name);
            }
        }
    }

    public void Register(ICab cab)
    {
        if (!cabs.ContainsValue(cab)) cabs.Add(cab.Name, cab);
    }

    private bool IsWithin5MileRadius(int cabLocation, int passengerLocation)
        => Math.Abs(cabLocation - passengerLocation) < 5;
}

Passenger Class

The Passenger class represents a passenger, holding information like name, address, and location. Passengers interact with the call center to book cabs.

namespace MediatorPattern.Demo;

internal interface IPassenger
{
    string Name { get; }
    string Address { get; }
    int Location { get; }

    void Acknowledge(string name);
}

internal class Passenger : IPassenger
{
    private string _name;
    private string _address;
    private int _location;

    public Passenger(string name, string address, int location)
    {
        _name = name;
        _address = address;
        _location = location;
    }
    public string Name => _name;

    public string Address => _address;

    public int Location => _location;

    public void Acknowledge(string name) =>
    Console.WriteLine($"Passenger {Name}, Cab: {name}");
}

Putting it Into Action

We instantiate the CallCenter and create instances of Cab and Passenger. The cabs register themselves with the call center, and passengers call the center to book cabs. The call center then handles the communication, assigning available cabs to passengers within the specified radius.

using MediatorPattern.Demo;

var callCenter = new CabCallCenter();

var passenger1 = new Passenger("Passender1", "123 Street", 10);
var passenger2 = new Passenger("Passender2", "456 Street", 25);

var cab1 = new Cab("Cab1", 11, true);
var cab2 = new Cab("Cab2", 22, true);

callCenter.Register(cab1);
callCenter.Register(cab2);

callCenter.BookCab(passenger1);
callCenter.BookCab(passenger2);

Console.ReadLine();

Conclusion

The Mediator design pattern proves invaluable in scenarios where direct communication between objects should be avoided. By centralizing the interaction through a mediator, we achieve loose coupling and greater flexibility in managing object interactions.

That wraps up our exploration of the Mediator pattern in .NET Core. If you found this blog post insightful, don’t forget to read the rest of my blog posts.

For more content like this, consider visiting my YouTube channel and subscribing to .Net Core Central. Thank you for joining me on this journey through the intricacies of design patterns!