The Mediator Design 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, its actual purpose is as a helper, that 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 explaining the exact purpose of this pattern that it helps us to achieve.
What is Mediator pattern?
According to the GoF's definition, this pattern's purpose is to:
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 it to the other class, for whom it was intended. And the response from the other class will be returned 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 the sources 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. The company is getting projects from various clients, say ClientA and ClientB. But due to time constraints, the development team cannot interact with the clients directly and 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, the 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 :
- ClientAbstract and DevTeamAbstract
This is nothing but simple abstract classes for some common functions that clients and the dev teams perform.
- ConcreteClients and ConcreteDevTeams
These are the concrete client and dev-teams, inheriting from their respective abstract classes. In our case, this will be the concrete classes ClientA and ClientB from the client abstract class and DevTeamA and DevTeamB inheriting from dev-team abstract classes.
- Mediator
This is the main component of the system, that will receive the requests from the sender and forward them to the appropriate receiver and vice-versa. Since the system is a 2-way communication type, all the communication will be handled by this class. So this class will have the methods to receive the requests from one component and send it to another, in both directions.
For this process, we will be writing the code in various steps and refer to them as different processes. But during this time, we need to keep in mind that all the communication is to be done through the mediator that is either for sending or receiving the response from the two components, in other words 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 and both the components will receive the reference to the mediator at run-time, in order to send/receive the messages from each other. The client and 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
The requirements are sent by the client to the mediator and the mediator forwards the requirements to the development team.
In this process, our client will send the requirements to the mediator and the mediator will forward them to the development team. Based on this concept, we have a method to send the requirements to the mediator in the client abstract class.
Step 1.1
The client abstract class will receive a reference of the mediator. Using this reference, the requirements will be handed over to the mediator, by calling the ReceieveRequirementsFromClient() method in the mediator.
Step 1.2
The Mediator will further pass 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. The 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, to allow them to use it for sending the messages.
At the same time, the Mediator class will be receiving the references of the client and dev teams to complete the communication process. We can see here that none of the client and dev team classes interact with each other directly. This results in a loosely-coupled system.
Step 2
The DevTeam sends queries to the mediator and the mediator forwards them to the clients.
In this step, the dev-team will send the query to the mediator and the mediator will forward the query to the client. Like the code above, the dev-team will have a method in its abstract class to forward the query to the mediator.
Step 2.1
The same as above, the 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
The Mediator has the reference to the Client class. The 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 ?
Since we have already set the reference of the mediator for the client and 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 the complexity of managing the objects and their references, but also promotes a loose coupling. Otherwise, when you add more clients and dev classes, without a mediator, each of these classes will have the references of the other concrete classes.
Mediator vs Adapter vs Bridge pattern
At the start, we explained various patterns that have quite similar names as this pattern. Its the main purpose for which these patterns are used, distinguish them. Let's explain briefly about their objectives.
The Adapter Pattern acts as an intermediate between two components, that cannot interact with each other directly. There is no other reason to do this. For example, a legacy code or a third-party component that needs to be integrated with your new implementations will require such a pattern to be used.
The purpose of a Bridge Pattern is to provide different implementations and each of these implementations can be used in various ways. This is done using the abstract components. For examle, send an SMS or email notification to a user and send them in various ways like using web-service or third-party tools.
The purpose of the Mediator Pattern is to manage the complexity of the system by handling how they interact with each other. Otherwise, each of these classes will have 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.