Mediator is a behavioral design pattern that lets you define an object that encapsulates relations between a set of objects to make them independent of each other.
Say you have a dialog for editing user profiles. It consists of various form fields such as
Some form elements have to interact with others. For instance, the "I have a dog" checkbox may show a hidden text field for entering the dog's name. Another example is the submit button that has to validate values of all fields before saving data.
By placing this logic directly into the form elements code, you make it much harder to reuse these classes in other forms of the app. For example, you can not use a checkbox inside another form, since it tightly coupled to the dog's text field. You can either use all classes or none at all.
The Mediator pattern prevents direct communications between individual components. Instead, they send requests to a mediator object that knows whom to direct these requests. Components lose dozens of dependencies and become related only to the mediator object.
In our example, the dialog class may represent a mediator. Probably, you will not even need to add any new dependencies into the class, since the dialog already knows about its child form elements.
The most significant change will happen inside form elements code. For example, let's consider the submit button. Previously, it had to be responsible for validating individual form elements when clicked. But now, its single job would be to just notify the dialog about the click. Upon receiving a submit notification, the dialog would do all the validations by itself. This way, instead of dozen relations to other form elements, the button will only be dependent on the dialog class.
But you can go further and make this dependency even looser by extracting the common interface for all dialogs. The interface would declare a method that will be used by all form elements to notify mediator about their events. Thus, our submit button will be able to communicate with any dialog that follows that interface.
This way the Mediator pattern wraps a web of relations between various components inside a single mediator object. The fewer dependencies has a class, the easier it becomes to modify, extend or reuse it.
Air traffic control
The control tower at a controlled airport demonstrates the Mediator pattern very well. The pilots of the planes approaching or departing the terminal area communicate with the tower rather than explicitly communicating with each other. The constraints on who can take off or land are enforced by the tower.
It is important to note that the tower does not control the whole flight. It exists only to enforce constraints in the terminal area.
Components are various objects containing business logic of a program. Each component stores a reference to a mediator. Components neither know nor care about the mediator's actual type since they work with it through the Mediator interface. This allows reusing components in other programs by linking them to a different mediator.
Components are not aware of other components. When their state change, they notify the mediator. Mediator knows whether any other component should do something about the event.
Mediator declares the interface of communication with Components which usually consists of a single method. Components may use this method to notify mediator about an important event happening to them. Components may pass themselves along with the event details to the notification method.
Mediator interface is crucial for reusing existing components in other programs. All you need to do is to provide a new Concrete Mediator that would control how components interact with each other in the new context.
Concrete Mediator encapsulates relations between various components. Mediators often keep references to all components they manage.
Concrete mediators are usually tightly coupled to their components. When mediator receives a notification, it can easily identify a sender. It also knows which component should be triggered in return. But from the component perspective it is a total black box. The sender does not know who end-up handling its event and the receiver does not know who issued the event in the first place.
In this example, the Mediator pattern helps to eliminate mutual dependencies between UI classes: buttons, checkboxes and text labels. When a user interacts with elements, they do not communicate directly, but rather notify a mediator about the event.
The authentication dialog acts as mediator. It knows how concrete elements should behave together and facilitates their cooperation. Upon receiving an event notification, it passes execution to the appropriate components.
When chaotic dependencies between components turn a simple change in one component into a series of changes in all components.
Mediator extracts relations between classes into a separate class making changes to one component isolated from the rest of the code.
When you can not reuse a component in a different program because it is too dependent on the other components.
After applying mediator, individual components become unaware of the other components. They communicate indirectly through a mediator object. Reusing a component in a different app requires creating a new mediator class.
When you have to create tons of component subclasses just to use the same components in different contexts.
Mediator encapsulates relations between components. Therefore, it is enough to create a new mediator subclass to define a new set of relations between the same components.
How to Implement
Identify a group of tightly coupled classes that will benefit from being more independent.
Create the Mediator interface and declare the communication protocol between the mediator object and various components. In most cases, a single method for receiving notifications from components would be sufficient.
Implement this method inside the Concrete Mediator class. The mediator class will benefit from storing references to all the components that have to be managed.
You can go further and move the component creation code into the mediator. This will turn mediator into a factory.
Components should also store a reference to the mediator. The connection is usually established by passing mediator object as an argument to the component's constructor.
Change the components code so that they call the mediator's notification method when something important happens. Extract the real business logic to the mediator class and execute it when mediator receives a notification from that component.
Pros and Cons
- Reduces coupling between components of a program.
- Allows reusing individual components.
- Centralizes the communications between various components.
- Mediator can evolve into a God Object.
Relations with Other Patterns
Chain of Responsibility passes a request sequentially along a dynamic chain of potential receivers until one of them handles a request.
Command establishes a one-directional connection from senders to receivers.
Mediator has senders and receivers reference each other indirectly.
Observer passes a request to all concerned receivers at the same time but allows them to subscribe and unsubscribe from receiving further requests dynamically.
Mediator is similar to Facade in that it abstracts functionality of existing classes. Mediator abstracts/centralizes arbitrary communications between colleague objects. It routinely "adds value", and it is known/referenced by the colleague objects. In contrast, Facade defines a simpler interface to a subsystem, it doesn't add new functionality, and it is not known by the subsystem classes.
The main goal of the Mediator pattern is to elimintate mutual dependencies between a set of system components. All components become dependent from a single mediator object instead. On the other hand, the goal of the Observer is to establish dynamic one-way connections between objects, where some objects act as subordinates of others.
There's a quite popular Mediator implementation that relies on the Observer pattern. The mediator object acts as publisher and all of the collegue components become subscribers. They dynamically subscribe and unsubscribe to the mediator's events. Implemented this way, both patterns may look very similar.
But the Mediator pattern can be implemented in different way, where all the components are permanently linked to the same mediator object. This implementation won't look as Observer but still be a case of the Mediator pattern.
Now imagine another program where all the components become publishers and allow dynamic connections with each other. There won't be a central mediator object, but rather distribute system of observers.