Mediator pattern is part of the behavioral design patterns. On the paths of bridge and adapter patterns, it might sound similar in concept that act as an intermediate. It actually does. But by being an intermediate, the aim it actually helps to achieve, is the main difference between this pattern and other patterns. In other words, its the basic goal of these patterns, that differentiate them from each other. So we will be discussing what aim exactly this pattern helps us to achieve.
What is Mediator pattern ?
According to GoF’s definition, this pattern is aimed at:
Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.
This definition actually means that a centralized class is to be used for interaction between two different classes. This will receive the input from one class and pass on to the another class, for whom it was intended. And the response from the other class will be sent back to the first class, from where the process started. So it results in a request-response system between the classes. This also results in a system where multiple classes from source can interact with multiple classes on the destination end and thus result in a loosely coupled system.
A real world example :
A development company is having a .NET development department. Company is getting projects from different clients say ClientA & ClientB. But due to time constraints, the development team cannot interact with the clients directly & vice-versa. So they decide to use an intermediate, say the Business development or BD team, to interact with the client. On the other hand, BD team will represent the client, to the development team. So basically the BD team here becomes an intermediate or mediator between the development team and the client.
Using the same example above, we will try to write the implementation in the code. Here, the mediator will perform the functionality to receive the requirements from a client, forward them to the dev-team, get queries from the dev-team and send them to the client.
To start with, we will have the following components in the system :
1. ClientAbstract & DevTeamAbstract: This is nothing but simple abstract classes for some common functions which clients & the dev teams perform.
2. ConcreteClients & ConcreteDevTeams: These are the concrete client & dev-teams, inheriting from their respective abstract classes. In our case, this will be the concrete classes ClientA & ClientB from the client abstract class and DevTeamA & DevTeamB inheriting from dev-team abstract classes .
3. Mediator : This is the main component of the system, which will receive the requests from the sender and forward them to the appropriate receiver & vice-versa. Since the system is of 2 way communication type, all the communication will be handled by this class. So this class will be having the methods to receive the requests from one component and send it to another, in both the directions.
For this process, we will be writing the code in different steps and refer to them as different processes. But during this time, we have to keep in mind that all the communication is to be done through the mediator which is either for sending or receiving the response from the two components i.e. the clients and the dev teams.
A very important key to this process is that the mediator contains the reference of both the interacting components within itself & both the components will receive the reference to the mediator at the run-time, in order to send/receive the messages from each other. Client & dev-team classes will have the reference of the mediator, through their base abstract classes.
Now, we will be writing the step based code.
Step 1 – Requirements sent by Client to the mediator and mediator forward the requirements to the development team :
In this process, our client will send the requirements to the mediator and mediator will forward them to the development team. Based on this concept, we will be having a method to send requirements to the mediator in the client abstract class.
Step 1.1 : The client abstract class will be receiving a reference of the mediator. Using this reference, requirements will be handed over to the mediator, by calling ReceieveRequirementsFromClient() method in the mediator.
Step 1.2 : Mediator will further pass on these to the DevTeam, using the reference of the DevTeam it holds, by calling the method ReceieveRequirementsFromMediator(client). This method is in the DevTeam class. Details received by the dev-team, will be displayed on the screen. See the code below :
Our client code to start the process will be like the following :
How Step 1 works ?
To start with the process, a new mediator reference is created, and will be given to the dev and client classes, in order to allow them to use it for sending the messages.
At the same time, Mediator class will be receiving the references of the client an dev teams, in order to complete the communication process. We can see here that none of the client & dev team classes interact with each other directly. This results in a loosely coupled system.
Step 2 – DevTeam sends queries to the mediator and mediator forwards them to the clients :
In this step, dev-team will send the query to the mediator and mediator will forward the query to the client. Like the code above, dev-team will have a method in its abstract class to forward the query to the mediator.
Step 2.1 : Same as above, Dev-team will receive the mediator reference, through its dev-team abstract class. using this reference, it will call the ReceiveQueryFromDevTeam method of the mediator.
Step 2.2 : Mediator is having the reference to the Client class. Mediator will use this reference to call the ReceiveQueryFromMediator method of the client class. This method will simply print out the details it has. See the code below :
How Step 2 works ?
As we have already set the reference of the mediator for the client & dev-team classes and references of these classes for the mediator in step 1, we simply need to call the SendQueryToMediator() of the dev-team class. So the client code will be :
With this structure in place, if we need to add one more dev-team or one more client, we simply need them to inherit from their respective base classes and set the appropriate references in the client code to get them working. So with this kind of code, we avoid each class having references to other class directly. This not only avoids complexity of managing the objects and their references, but also promotes loose coupling. Otherwise, when you will be adding more clients and dev classes, without mediator, each of these classes will be having the references of the other concrete classes.
Mediator vs Adapter vs Bridge pattern :
At the start, we discussed different patterns which have quite similar names as this pattern. Its the main purpose for which these patterns are used, distinguish them. Let’s discuss briefly about their objectives.
Adapter pattern is to act as an intermediate between two components, which cannot interact with each other directly. No other reason to do this. For ex, a legacy code or a third party component which needs to be integrated with your new implementations will require such a pattern to be used.
Bridge pattern is aimed at providing different implementations and and each of these implementations can be used in different ways. This is achieved using the abstract components. For ex, send an SMS or email notification to user and send them in different ways like using web-service or third party tools.
Mediator pattern is aimed at managing the complexity of the system by handling how they interact with each other. Otherwise, each of these classes will be having references to the other classes in order to interact, when required. This would result in a spider web type system classes. So this can be avoided by using the mediator pattern.
Hope you enjoyed reading this article.