Design patterns: Mediator

in #design-patterns7 years ago

Mediator_example.png

Today it was supposed to be about an operational pattern called Mediator, which satisfies the last SOLID principle, namely avoiding dependencies between classes, here we create one class that can be said is an interface to all elements of the system, we pass only commands to this interface and it handles the transfer , the mediator is certainly a pattern worth knowing … 💪 so we’re going with the topic 🙂.


Admission

Soon we will go to a specific Mediator discussion, only a small news, after the publication of this entry may not be another entry for a month, two months or more, because I would like to deal more with the translation of this blog so that not only Polish had access to it and I would like to improve its functionality and some other entries and of course I also have other projects and I would like a little rest 😓 , so it may take a while, the blog treats temporarily as an add-on, as I said I have other projects, but it should change in the future because I like to help others. Ok, let’s get to the facts 🙂

Discussion

The Mediator pattern gives a uniform interface for sending messages between classes, this interface contains all references to these classes, i.e. it simply reduces the number of dependencies between them, if for example we need a dependency or element from another class, we send a command to the mediator interface, and this returns us appropriate value.

If we wanted to write dependencies in the traditional way, i.e. simply to stuff dependencies to classes that depend on these dependencies, then there is a tangle of dependencies in these classes let us assume that we have group of three people who are administers, devs and users, the dependencies look like this as below in the picture.

Mediator.png

A tangle of dependencies emerges from such a traditional operation, it is unacceptable in larger projects. And if we used the Mediator pattern then we would have something like this:

Mediator_1.png

And you can see that it not only looks better and is much clearer by that arrangement objects do not know about themselves and the less objects know about themselves the better, there is less probability of error and if they want to get some dependence from another class they just send command to the mediator interface and this one returns the appropriate value.

Intent

  1. Reducing the number of connections between classes.
  2. Encapsulation of objects using the mediator interface.
  3. Providing a unified interface to managing dependencies between classes.

Problem

It is best to use the mediator’s pattern in situations where we have many dependencies between classes, and so-called spaghetti code is created, which is difficult to understand 🙂 . It’s best to learn as fast as you can how to write a non-spaghetti code. 😐

Use when:

  1. You want to get rid of many dependencies between classes.
  2. You want to encapsulate classes so that they do not know about themselves, only took dependencies through the mediator class.
  3. You want to have one interface in which all class references will be collected.

Advantages and disadvantages

Advantages

  1. Loose relationships between objects in the system.
  2. Dependencies between objects are flexible, it is possible to easily develop dependencies.
  3. Because the whole logic is encapsulated in the mediator class, if we need to add a dependency to a class, we only need to expand the mediator class.
  4. Simplified communication between classes, because if a class wants to communicate with some other classes, it only has to send a command to the mediator class.

Disadvantages

  1. Complicating the Mediator class, if there are too many dependencies in it, which will be responsible for everything, similarly works on the first SOLID principle, i.e. if Mediator class dependencies are responsible only for communication, it is better that there are no dependencies in this class responsible for something else, because then Mediator classes will be very complicated.

That’s a bit of it is, I’m aware of it, that’s why I’ll tell prank, so that I and you will also rest. 🙂😂

The patient comes to the doctor and asks:

– Doctor, how long will I live !?

-Hmm, are you drinking?

-No! I never drank !!

– Have you smoke?

-No! Also never!

-Some women did you have?

– No! So, Doctor, how long will I live ?!

– Hundred years I think … but why the fk like that?**

Well, the end of the prank 😂, let’s move on 🙂

Structure

The general UML diagram of the Mediator pattern looks like this:

Mediator__1.png

It shows that the classes communicate through the Mediator class, but usually the detailed implementation of the Mediator pattern looks like in the diagram below:

Mediator___1.png

In the diagram above, the classes inheriting from the Widget class, ie Table, Tree, CheckBox, use the Intermediary class, which is the equivalent of the Mediator class from the first diagram.

In the code, the mediator pattern looks more or less like the one below. As usual, in the structure area gives a simple example to make it easy to understand, let’s start with the Mediator class:

namespace MediatorSchema
{
    abstract class Mediator
    {
        public abstract void Send(string message, Colleague colleague);
    }
 
    class ConcreteMediator : Mediator
    {
        private Colleague _colleague1;
        private Colleague _colleague2;
 
        public Colleague Colleague1
        {
            set { _colleague1 = value; }
        }
 
        public Colleague Colleague2
        {
            set { _colleague2 = value; }
        }
 
        public override void Send(string message, Colleague colleague)
        {
            if (colleague == _colleague1)
            {
                _colleague2.Notify(message);
            }
            else
 
            {
                _colleague1.Notify(message);
            }
        }
    }
}

We see that arguments are sent to the Send() method and, depending on the type of class passed, pass the message variable to the appropriate class. At the top, we have the Colleague type set twice, so that we do not create a specific class type, and we pass concrete objects to the ConcreteMediator class in the client.

namespace MediatorSchema
{
    class Program
    {
        static void Main(string[] args)
        {
            ConcreteMediator m = new ConcreteMediator();
 
            ConcreteColleague1 c1 = new ConcreteColleague1(m);
            ConcreteColleague2 c2 = new ConcreteColleague2(m);
 
            m.Colleague1 = c1;
            m.Colleague2 = c2;
 
            c1.Send("How are you?");
            c2.Send("Fine, thanks");
 
            Console.ReadKey();
        }
    }
}

And also ConcreteColleague1 and ConcreteColleague2 classes.

{
    abstract class Colleague
    {
        protected Mediator mediator;
 
        public Colleague(Mediator mediator)
        {
            this.mediator = mediator;
        }
 
        public abstract void Send(string message);
        public abstract void Notify(string message);
    }
 
    class ConcreteColleague1 : Colleague
    {
        public ConcreteColleague1(Mediator mediator) : base(mediator) { }
 
        public override void Send(string message)
        {
            mediator.Send(message, this);
        }
 
        public override void Notify(string message)
        {
            Console.WriteLine("Colleague1 gets message: " + message);
        }
    }
 
    class ConcreteColleague2 : Colleague
    {
        public ConcreteColleague2(Mediator mediator) : base(mediator) { }
 
        public override void Send(string message)
        {
            mediator.Send(message, this);
        }
 
        public override void Notify(string message)
        {
            Console.WriteLine("Colleague2 gets message: " + message);
        }
    }
}

When we return to the client, Send() methods are called in the ConcreteColleague1 and ConcreteColleague2 classes, which only forward the message to the ConcreteMediator class and depending which class invoked the Send() method, the corresponding message from the Notify() method is displayed.

Result:

mediatorschema.png

Real-life examples

Chat

The first such example will be chat, for example, if we were to divide the code into classes according to each chat user, and for each of them to assign dependencies, the dependencies between them would be very tangled, and in this example it is good to use a mediator if a person sends a message, she is transferred to the mediator’s class and then passed to the appropriate person who was to receive the message. And all dependencies we have in one class, beautifully simple, clear and effective.

Let’s see how it looks in the code, let’s start with the mediator class.

namespace ChatRoom
{
    abstract class AbstractChatroom
    {
        public abstract void Register(Participant participant);
        public abstract void Send(string from, string to, string message);
    }
 
    class Chatroom : AbstractChatroom
    {
        private Dictionary<string, Participant> _participants = new Dictionary<string, Participant>();
 
        public override void Register(Participant participant)
        {
            if (!_participants.ContainsValue(participant))
            {
                _participants[participant.Name] = participant;
            }
 
            participant.Chatroom = this;
        }
 
        public override void Send(string from, string to, string message)
        {
            Participant participant = _participants[to];
 
            if (participant != null)
            {
                participant.Receive(from, message);
                participant.Notify(from);
            }
        }
    }
}

The principle of the mediator’s class is the same as in the previous example only the implementation is different, here we do not assign on stiffly the dependencies only with the Register() method. We register dependencies on a regular basis, check if there is such a dependency in the class, if not, we write it to the dictionary.

And in the Send() method we check if there is a registered person with name, which we passed to the Send() method in the chat, if so we send a message to it.

Let’s see what the Participant class looks like, which we defined in the dictionary.

namespace ChatRoom
{
    abstract class Participant
    {
        private string _name;
        public string Name
        {
            get { return _name; }
        }
 
        private AbstractChatroom _chatroom;
        public AbstractChatroom Chatroom
        {
            set { _chatroom = value; }
            get { return _chatroom; }
        }
 
        public Participant(string name)
        {
            _name = name;
        }
 
        public void Send(string to, string message)
        {
            _chatroom.Send(_name, to, message);
        }
 
        public abstract void Receive(string from, string message);
        public abstract void Notify(string from);
    }
}

The most important thing in this class is that we write down the name of the participant in it and forward the message to the Send() method in the Participant class and then further forward the message to the Send() method in the Chatroom class, and we have the Receive() and Notify() methods defined.

As I divided participants according to their gender, we have separate classes for them.

namespace ChatRoom
{
    class Female : Participant
    {
        public Female(string name) : base(name) { }
 
        public override void Receive(string from, string message)
        {
            Console.WriteLine("{0} to {1}: '{2}'", from, Name, message);
        }
 
        public override void Notify(string from)
        {
            Console.WriteLine("Woman " + Name + " gets message from: " + from);
        }
    }
 
    class Male : Participant
    {
        public Male(string name) : base(name) { }
 
        public override void Receive(string from, string message)
        {
            Console.WriteLine("{0} to {1}: '{2}'", from, Name, message);
        }
 
        public override void Notify(string from)
        {
            Console.WriteLine("Man " + Name + " gets message from: " + from);
        }
    }
}

And in these classes we have defined Receive() and Notify() methods.

Finally, the customer.

namespace ChatRoom
{
    class Program
    {
        static void Main(string[] args)
        {
            Chatroom chatroom = new Chatroom();
 
            Participant George = new Male("George");
            Participant Jasmine = new Female("Jasmine");
            Participant Ringo = new Male("Ringo");
            Participant John = new Male("John");
            Participant Paul = new Male("Paul");
 
            chatroom.Register(George);
            chatroom.Register(Paul);
            chatroom.Register(Ringo);
            chatroom.Register(John);
            chatroom.Register(Jasmine);
 
            Jasmine.Send("John", "Hi John!");
            George.Send("Ringo", "Hi Ringo! How are you!");
            Ringo.Send("George", "Hi George!");
            Paul.Send("John", "Hi everyone!");
            John.Send("George", "My homies in one place!!");
 
            Console.ReadKey();
        }
    }
}

And you can see that in the client we register chat users and send messages.

Result:

chatroom.png

Control platform

Another example is the flight control tower, I bet that the software uses Mediator 🙂 there, this tower controls the flight of every plane, helicopter, etc. This is well illustrated by the picture below.

Mediator_example.png

Let’s start as in the previous example about the chat from the mediator class.

namespace ControlFlight
{
    abstract class TowerMediator
    {
        public abstract void Register(Machine machine);
        public abstract void SendControlMessage(string typeMachine);
    }
 
    class ControlTower : TowerMediator
    {
        private Dictionary<string, Machine> _machines = new Dictionary<string, Machine>();
 
        public override void Register(Machine machine)
        {
            if (!_machines.ContainsValue(machine))
            {
                _machines[machine.TypeMachine] = machine;
            }
 
            machine.Controltower = this;
        }
 
        public override void SendControlMessage(string typeMachine)
        {
            Machine machine = _machines[typeMachine];
 
            if (machine != null)
            {
                Console.WriteLine("Control the flight of a {0}", machine.TypeMachine);
            }
        }
    }
}

The principle of operation and implementation, identical to the chat example, the Register() method registers the dependencies and the SendControlMessage() method checks whether the transferred machine exists in the dictionary, if so we display the corresponding message.

Let’s see how the Machine class looks like.

namespace ControlFlight
{
    abstract class Machine
    {
        protected string _typemachine;
        public string TypeMachine
        {
            get { return _typemachine; }
        }
 
        protected TowerMediator _controltower;
        public TowerMediator Controltower
        {
            set { _controltower = value; }
            get { return _controltower; }
        }
 
        public Machine(string typemachine)
        {
            _typemachine = typemachine;
        }
 
        public abstract void SendControlMessage();
    }
}

Very similar to the Participant class, only the SendControlMessage() method is defined here, it is implemented in the classes responsible for the machine types that are below.

namespace ControlFlight
{
    class SailPlane : Machine
    {
        public SailPlane() : base("Sailplane")
        { }
 
        public override void SendControlMessage()
        {
            Console.WriteLine("SailPlane Configuration");
            _controltower.SendControlMessage(_typemachine);
        }
    }
 
    class Hunter : Machine
    {
        public Hunter() : base("Hunter")
        { }
 
        public override void SendControlMessage()
        {
            Console.WriteLine("Hunter Configuration");
            _controltower.SendControlMessage(_typemachine);
        }
    }
 
    class Helicopter : Machine
    {
        public Helicopter() : base("Helicopter")
        { }
 
        public override void SendControlMessage()
        {
            Console.WriteLine("Helicopter Configuration");
            _controltower.SendControlMessage(_typemachine);
        }
    }
}

It can be seen that in these classes we call the SendControlMessage() method defined in the ControlTower class to which we pass the name of the machine and check in it whether such a machine is registered.

Result

controltower.png

Relations with other design patterns

  1. The mediator is similar to the facade in that it simplifies the existing functionality, but the difference is that the Mediator only simplifies the communication between classes and the facade creates an interface, thanks to which it will simply be easier to use existing functionalities.

Summary

That’s all about Mediator 🙂.

Link to github with the whole code from this article: https://github.com/Slaw145/MediatorTutorial

This content also you can find on my blog http://devman.pl/programtech/design-patterns-mediator/

If you recognise it as useful, share it with others so that others can also use it.

Leave upvote and follow and wait for next articles :) .

In the next article, we will talk about the Template Method pattern.

And NECESSERILY join the DevmanCommunity community on fb, part of the community is in one place 🙂

– site on fb: Devman.pl-Sławomir Kowalski

– group on fb: DevmanCommunity

Ask, comment underneath at the end of the post, share it, rate it, whatever you want🙂.

Illustrations, pictures and diagrams are from: https://sourcemaking.com/design_patterns/mediator