Mediator

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

Introduction

  • Mediator Pattern is one of the Patterns in the Behavior Pattern group. Mediator means middleman. This pattern says, “The definition of an object encapsulates how a set of objects interact.

  • Mediator promotes loose coupling by preventing objects from referring to each other explicitly, and it allows you to vary their interactions independently.

  • Mediator Pattern is similar to Adapter Pattern but is used for a different purpose.

  • Mediator Pattern acts as a bridge.

Problem

Say you have a dialog for creating and editing customer profiles. It consists of various form controls such as text fields, checkboxes, buttons, etc.

Some of the form elements may interact with others. For instance, selecting the “I have a dog” checkbox may reveal 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 the data.

By having this logic implemented directly inside the code of the form elements you make these elements’ classes much harder to reuse in other forms of the app. For example, you won’t be able to use that checkbox class inside another form, because it’s coupled to the dog’s text field. You can use either all the classes involved in rendering the profile form, or none at all.

Solution

The Mediator pattern suggests that you should cease all direct communication between the components which you want to make independent of each other. Instead, these components must collaborate indirectly, by calling a special mediator object that redirects the calls to appropriate components. As a result, the components depend only on a single mediator class instead of being coupled to dozens of their colleagues.

In our example with the profile editing form, the dialog class itself may act as the mediator. Most likely, the dialog class is already aware of all of its sub-elements, so you won’t even need to introduce new dependencies into this class.

The most significant change happens to the actual form elements. Let’s consider the submit button. Previously, each time a user clicked the button, it had to validate the values of all individual form elements. Now its single job is to notify the dialog about the click. Upon receiving this notification, the dialog itself performs the validations or passes the task to the individual elements. Thus, instead of being tied to a dozen form elements, the button is only dependent on the dialog class.

You can go further and make the dependency even looser by extracting the common interface for all types of dialogs. The interface would declare the notification method which all form elements can use to notify the dialog about events happening to those elements. Thus, our submit button should now be able to work with any dialog that implements that interface.

This way, the Mediator pattern lets you encapsulate a complex web of relations between various objects inside a single mediator object. The fewer dependencies a class has, the easier it becomes to modify, extend or reuse that class.

Real-World Analogy

Pilots of aircraft that approach or depart the airport control area don’t communicate directly with each other. Instead, they speak to an air traffic controller, who sits in a tall tower somewhere near the airstrip. Without the air traffic controller, pilots would need to be aware of every plane in the vicinity of the airport, discussing landing priorities with a committee of dozens of other pilots. That would probably skyrocket the airplane crash statistics.

The tower doesn’t need to control the whole flight. It exists only to enforce constraints in the terminal area because the number of involved actors there might be overwhelming to a pilot.

Structure

Components in the model:

  • Components are different classes that contain some business logic such as Button, TextField, etc. Each component has a reference to a Mediator, declared with the type Mediator interface. Component doesn't care about the actual Mediator classes. Therefore, it is possible to reuse the component in other programs and simply associate it with another mediator.

  • The Mediator interface declares methods to communicate with components, usually including only a single notification method. Components can pass any context as arguments to this method, including their objects, but only in such a way that no coupling occurs between the receiving component and the sending class.

  • Concrete Mediator encapsulates the relationships between different components. Concrete mediators typically keep references to all the components they manage and often even manage the entire lifecycle.

  • Components do not need to care about other components. If anything happens to the component, they only need to notify the mediator. When the mediator receives the message, it can easily determine where to send it (this may be just enough to decide which component should be activated).

Advantages & disadvantages

Advantage

  • Ensuring the Single Responsibility Principle (SRP): we can extract the communication between different components into a single place, making it easier to maintain.

  • Ensuring the Open/Closed Principle (OCP): we can create new mediators without changing components.

  • Minimize coupling between different components in a program.

  • Reusing components is simpler.

  • Simplifying communication between objects, a Mediator will replace the many-to-many relationship between components with a one-to-many relationship between a mediator and components. .

  • Centralized management helps clarify how components interact within the system

Disadvantage

  • Over time, the Mediator can become a God object.

When to use it

  • Use when it is difficult to change some layers because they are tightly connected to many other layers.

  • Use when a component cannot be reused in other programs because it is too dependent on other components.

  • Use when you feel like you're creating a lot of component subclasses just to reuse a few simple behaviors in different contexts.

  • Use when a collection of objects communicates in clearly defined ways but the way is too complex. Interdependencies between objects result in unstructured and confusing organization.

  • Use when you need to reuse an object but it is difficult because it references and communicates with many other objects.

  • Use when adjusting behavior between layers easily, without needing to edit multiple layers.

  • Often used in message-based systems, such as chat systems.

  • When communication between objects in the system is too complex, there are too many relationships between objects in the system. A common point for control or communication is needed.

Applicability

  • Use the Mediator pattern when it’s hard to change some of the classes because they are tightly coupled to a bunch of other classes.

    • The pattern lets you extract all the relationships between classes into a separate class, isolating any changes to a specific component from the rest of the components.

  • Use the pattern when you can’t reuse a component in a different program because it’s too dependent on other components.

    • After you apply the Mediator, individual components become unaware of the other components. They could still communicate with each other, albeit indirectly, through a mediator object. To reuse a component in a different app, you need to provide it with a new mediator class.

  • Use the Mediator when you find yourself creating tons of component subclasses just to reuse some basic behavior in various contexts.

    • Since all relations between components are contained within the mediator, it’s easy to define entirely new ways for these components to collaborate by introducing new mediator classes, without having to change the components themselves.

How to Implement

  1. Identify a group of tightly coupled classes which would benefit from being more independent (e.g., for easier maintenance or simpler reuse of these classes).

  2. Declare the mediator interface and describe the desired communication protocol between mediators and various components. In most cases, a single method for receiving notifications from components is sufficient.

    This interface is crucial when you want to reuse component classes in different contexts. As long as the component works with its mediator via the generic interface, you can link the component with a different implementation of the mediator.

  3. Implement the concrete mediator class. Consider storing references to all components inside the mediator. This way, you could call any component from the mediator’s methods.

  4. You can go even further and make the mediator responsible for the creation and destruction of component objects. After this, the mediator may resemble a factory or a facade.

  5. Components should store a reference to the mediator object. The connection is usually established in the component’s constructor, where a mediator object is passed as an argument.

  6. Change the components’ code so that they call the mediator’s notification method instead of methods on other components. Extract the code that involves calling other components into the mediator class. Execute this code whenever the mediator receives notifications from that component.

Relations with Other Patterns

  • Chain of Responsibility, Command, Mediator and Observer address various ways of connecting senders and receivers of requests:

    • Chain of Responsibility passes a request sequentially along a dynamic chain of potential receivers until one of them handles it.

    • Command establishes unidirectional connections between senders and receivers.

    • Mediator eliminates direct connections between senders and receivers, forcing them to communicate indirectly via a mediator object.

    • Observer lets receivers dynamically subscribe to and unsubscribe from receiving requests.

  • Facade and Mediator have similar jobs: they try to organize collaboration between lots of tightly coupled classes.

    • Facade defines a simplified interface to a subsystem of objects, but it doesn’t introduce any new functionality. The subsystem itself is unaware of the facade. Objects within the subsystem can communicate directly.

    • Mediator centralizes communication between components of the system. The components only know about the mediator object and don’t communicate directly.

  • The difference between Mediator and Observer is often elusive. In most cases, you can implement either of these patterns; but sometimes you can apply both simultaneously. Let’s see how we can do that.

The primary goal of Mediator is to eliminate mutual dependencies among a set of system components. Instead, these components become dependent on a single mediator object. The goal of Observer is to establish dynamic one-way connections between objects, where some objects act as subordinates of others.

There’s a popular implementation of the Mediator pattern that relies on Observer. The mediator object plays the role of publisher, and the components act as subscribers which subscribe to and unsubscribe from the mediator’s events. When Mediator is implemented this way, it may look very similar to Observer.

When you’re confused, remember that you can implement the Mediator pattern in other ways. For example, you can permanently link all the components to the same mediator object. This implementation won’t resemble Observer but will still be an instance of the Mediator pattern.

Now imagine a program where all components have become publishers, allowing dynamic connections between each other. There won’t be a centralized mediator object, only a distributed set of observers.

Last updated