Design Patterns - Mediator

01 juli 2022 om 10:00 by ParTech Media - Post a comment

In our previous blog of the design pattern series, we had discussed the iterator design pattern, which falls under the category of behavioral design patterns. Today, we will explore another design pattern from the same category called Mediator Design Pattern.

Table of contents

  1. Introduction to Mediator Design Pattern
  2. Implementation of Mediator Design Pattern
  3. Conclusion

Introduction to Mediator Design Pattern

The Mediator design pattern offers a class within which interaction between different objects of classes happens. For instance, if we have a requestor class and processing class, then the request from the requestor class goes through a mediator class which has the logic to call the required processing class. It determines how the objects interact and process the request.

Since the mediator class acts as the hub for interaction, it reduces the dependent classes which are usually required for interaction. It provides a loosely coupled architecture by removing the direct communication between the requestor and processing classes.

Implementation of Mediator Design Pattern

The Mediator design pattern mainly consists of:

  • Mediator
  • Concrete Mediator
  • Colleague
  • Concrete Colleague

Here is a template to depict the working of the Mediator Design Pattern -

using System;

namespace PARTECH_Mediator
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var mediator = new ConcreteMediator();
            var colleagueA = mediator.ColleagueA = new ConcreteColleagueA(mediator);
            var colleagueB = mediator.ColleagueB = new ConcreteColleagueB(mediator);

            colleagueA.DoOperation("Sending input from A to B.");

            colleagueB.DoOperation("Sending input from B to A.");

            Console.ReadLine();
        }
    }

    public abstract class Colleague
    {
        protected IMediator mediator;

        public Colleague(IMediator mediator)
        {
            this.mediator = mediator;
        }
    }

    public interface IMediator
    {
        void DoOperation(string input, Colleague caller);
    }

    public class ConcreteColleagueA: Colleague
    {
        public ConcreteColleagueA(IMediator mediator): base(mediator) { }

        public void DoOperation(string input)
        {
            Console.WriteLine($"From Colleague A - Operation started for messsage - { input }");
            mediator.DoOperation(input, this);
        }

        public void EndOperation(string message)
        {
            Console.WriteLine($"Received message to end operation. - { message }");
        }

    }

    public class ConcreteColleagueB : Colleague
    {
        public ConcreteColleagueB(IMediator mediator) : base(mediator) { }

        public void DoOperation(string input)
        {
            Console.WriteLine($"From Colleague B - Operation started for messsage - { input }");
            mediator.DoOperation(input, this);
        }

        public void EndOperation(string message)
        {
            Console.WriteLine($"Received message to end operation. - { message }");
        }
    }

    public class ConcreteMediator: IMediator
    {
        public ConcreteColleagueA ColleagueA { get; set; }
        public ConcreteColleagueB ColleagueB { get; set; }

        public void DoOperation(string input, Colleague caller)
        {
            if (caller == ColleagueA)
            {
                ColleagueB.EndOperation(input);
            }
            else
            {
                ColleagueA.EndOperation(input);
            }
        }

    }
}

Here,

  • A colleague is an abstract class. It has a constructor to take a mediator class as input.
  • IMediator is the interface that provides the signature for the mediator class. For our understanding, we have added a method in that interface.
  • Concrete Colleague A and B are the classes that implement Colleague abstract class and have two methods in it - DoOperation and EndOperation. DoOperation calls the mediator DoOperation internally for execution in both Colleague A and B concrete class.
  • ConcreteMediator is a class that implements the IMediator interface and has properties to store Colleague A and B. It implements the IMediator method and performs a check based on the caller. The other colleague's DoOperation method is then called.
  • The main method is the client here, where objects for Concrete Colleague A and B and concrete mediator are created. Colleague A and B objects are assigned to the respective properties of the mediator object. And the DoOperation method is called from both Colleague A and B objects.

On executing the above code, the below output gets printed.

Now that we have understood the basic template to implement the Iterator design pattern let us implement the same through a practical scenario.

A mediator pattern can be used in areas where two entities require a medium to communicate. An ideal example would be a web messenger, which we come across regularly. Let us try to implement a simple messenger code and understand how the mediator pattern is used in it.

using System;
using System.Collections.Generic;

namespace PARTECH_Mediator
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var hub = new Hub();
            var user1 = new PersonalUser(hub, "User1");
            var user2 = new PersonalUser(hub, "User2");
            var user3 = new PersonalUser(hub, "User3");
            var user4 = new PersonalUser(hub, "User4");
            var user5 = new PersonalUser(hub, "User5");

            hub.Register(user1);
            hub.Register(user2);
            hub.Register(user3);
            hub.Register(user4);
            hub.Register(user5);

            user1.Send("User3", "Parcel received.");
            user2.Send("User3", "Thanks for sending the parcel.");
            user3.Send("User1", "Thanks for confirming.");
            user4.Send("User2", "Thnaks for informing user 3 about the parcel status.");
            user5.Send("User4", "Thanks for keeping track for user 3.");
            Console.ReadLine();
        }
    }

    public abstract class User
    {
        public IHub hub;

        public string Name { get; set; }

        public User(IHub hub, string name)
        {
            this.hub = hub;
            Name = name;
        }

        public abstract void Send(string to, string message);

        public abstract void Receive(string from, string message);
    }

    public class PersonalUser : User
    {
        public PersonalUser(IHub hub, string name) : base(hub, name) { }
        public override void Send(string to, string message)
        {
            hub.SendMessage(message, Name, to);
        }

        public override void Receive(string from, string message)
        {
            Console.WriteLine($"{from} to {Name}: '{message}'");
        }
    }

    public interface IHub
    {
        void SendMessage(string message, string from, string to);
        void Register(User user);
    }

    public class Hub : IHub
    {
        private Dictionary<string, User> participants = new Dictionary<string, User>();
        public void Register(User participant)
        {
            if (!participants.ContainsValue(participant))
            {
                participants[participant.Name] = participant;
            }
            participant.hub = this;
        }
        public void SendMessage(string message, string from, string to)
        {
            var participant = participants[to];
            if (participant != null)
            {
                participant.Receive(from, message);
            }
        }
    }
}

In the above code, IHub is the IMediator, Hub is the concrete mediator, User is the Abstract Colleague, and Personal user is the concrete colleague. The Main method is the consuming class.

We have only one Concrete Colleague here. However, we can have multiple concrete colleagues if needed. In the main method, an object for the hub is created. Few objects for the PersonalUser class are also created by passing the object of the hub and the name of the PersonalUser. Once that is done, the users are registered with the hub for communication, and the message is sent from one user to another. On executing the above program, the below output gets generated.

Conclusion

And that’s what a Mediator Behavioral design pattern is all about. In this post, we have also seen how to implement it in a real-world scenario. Stay tuned for the next blog on the Design Patterns series.