22 - Mediator
Tags: flutter, dart, oop, programming
Published: 2021-06-14 06:27:49

Previously in the series, I have analysed a behavioural design pattern that separates algorithms from the objects they operate on — Visitor. This time I would like to represent once another behavioural design pattern that lets you reduce dependencies between a set of interacting objects by decoupling the interaction logic from the objects and moving it to a dedicated controller — it is the Mediator.

## Table of Contents

* What is the Mediator design pattern?
* Analysis
* Implementation
* Your Contribution

## What is the Mediator design pattern?

![The Importance Of A Mediator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/femw0y595x1olix0iww2.gif)

**Mediator**, also known as **Intermediary** or **Controller**, is a behavioural design pattern, which intention in the [GoF book](https://en.wikipedia.org/wiki/Design_Patterns) is described like this:

> 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.

TL;DR: the main target of the Mediator design pattern is to move from the communicating object dependencies chaos provided on the left to the decoupled one provided on the right:

![Object Dependencies Map](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/okm132uiy7cwzd5bzrss.png)

In the Mediator design pattern context, communicating objects are called **colleagues** while the object that controls and coordinates the interaction is called \**drums rolls*\* the **mediator**.

The mediator is like a telephone exchange that keeps references to interacting objects and maintains all the required logic to “connect” colleague A with colleague B. As a result, colleague objects have no explicit knowledge of each other, they only refer to their mediator — in the OOP world, we could say that objects are loosely coupled. This allows reusing individual colleague objects independently since they have fewer dependencies on the other objects.

Another upside of the Mediator design pattern is that it simplifies and abstracts the way of how objects interact. First of all, the mediator replaces many-to-many (N:M) relationships with one-to-many (1:N) interactions between the mediator and its colleagues. In general, 1:N relationships are just easier to understand and maintain. Besides, the mediator object abstracts the interaction logic — colleagues should be aware only of the communication act but not of any details on how it is implemented. This abstraction enables adding new mediators without changing the actual components. Also, having the whole communication logic in a single place helps a lot when you need to adjust or maintain it.

Let’s just jump right in by analysing the Mediator design pattern and its implementation in more detail!

## Analysis

The general structure of the Mediator design pattern looks like this:

![Mediator Class Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/msxbdw39vwj52qa2st3s.png)

* *Mediator* — defines an interface for communicating with components;
* *ConcreteMediator* — encapsulates relations between components by containing references to them;
* *(Optional) Abstract Component* or *Component Interface* — similar communicating components could implement the same interface or extend the same base class. In this case, *ConcreteMediator* could store a list of components extending/implementing this class instead of keeping multiple references as separate properties;
* *Concrete component* or *Colleague* — contains a reference to a mediator. Each colleague communicates with its mediator whenever it would have otherwise communicated with another colleague (component).

### Applicability

The Mediator design pattern should be used when instead of having tightly coupled classes you want to have loose-coupled ones, because:

a) You want to reuse the component elsewhere. When a component is too dependent on other classes, it’s hard to reuse it as a stand-alone object.
b) You want to make changes in some of the classes, but they affect other dependencies. By using the Mediator design pattern, the relationship logic between objects is extracted to a separate class, hence the changes could be implemented without directly affecting the rest of the components.

Also, you should consider using the Mediator design pattern when there is a need to add communicating objects at run-time. Since the mediator class takes care of the communication logic and all the dependencies between objects, it’s possible to add or remove those dependencies later from the code just like adding a new user to the chat room.

However, by moving all the communication logic to a dedicated class there is a risk to end up having a [God Object](https://en.wikipedia.org/wiki/God_object). To avoid this, make sure that the mediator class is only responsible for the communication part. If you notice any other calculations, data manipulations or extraneous operations (Eminem would be proud of this line, I think) they should be extracted to a dedicated class.

## Implementation

![Let's Start Coding](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ll5rm4927v5w35u0bb1m.gif)

We will use the Mediator design pattern to implement a notification hub for the engineering team.

Let’s say that we want a solution to send notifications to other team members. Inside the team, there are 3 main roles: Admin a.k.a. God, Developer and tester (QA engineer). There are times when the admin wants to send notifications to the whole team or members of a specific role. Also, any other team member should be able to send a quick note to the whole team, too.

If you think of this problem, you could quickly notice a many-to-many relationship between team members — every engineer should be aware of the others just to send the notification. For this reason, we will implement a centralised way to send notifications — a notification hub. You could think of it as a chat room — every team member joins the hub and later they use it to send notifications by simply calling a *send* method. Then, the hub distributes the message to the others — to all of them or by specific role.

By using this solution, team members should not be aware of the others, they are completely decoupled. Also, in the case of a new team member, it is enough to add him/her to the notification hub and you could be sure that all the notifications would be delivered.

Sounds too good to be true? Watch and learn!

### Class diagram

The class diagram below shows the implementation of the Mediator design pattern:

![Mediator Implementation Class Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rszynutfh3e2tgk7tthg.png)

*TeamMember* is an abstract class that is used as a base class for all the specific team member classes. The class contains *name*, *lastNotification* and *notificationHub* properties, provides several methods:

* *receive()* — receives the notification from the notification hub;
* *send()* — sends a notification;
* *sendTo<T>()* — sends a notification to specific team members.

*Admin*, *Developer* and *Tester* are concrete team member classes that extend the abstract class *TeamMember* as well as override the default *toString()* method.

*NotificationHub* is an abstract class that is used as a base class for all the specific notification hubs and defines several abstract methods:

* *getTeamMembers()* — returns a list of team members of the hub;
* *register()* — registers a team member to the hub;
* *send()* — sends a notification to registered team members;
* *sendTo<T>()* — sends a notification to specific registered team members.

*TeamNotificationHub* is a concrete notification hub that extends the abstract class *NotificationHub* and implements its abstract methods. Also, this class contain a list of registered team members — *teamMembers*.

*MediatorExample* initialises and contains a notification hub property to send and receive notifications, register team members to the hub.

### TeamMember

An abstract class implementing base methods for all the specific team member classes. Method *receive()* sets the *lastNotification* value, *send()* and *sendTo<T>()* methods send notification by using the corresponding *notificationHub* methods.

![team_member.dart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ecn3ytr1t25ko5whl7f.png)

### Concrete team member classes

All of the specific team member classes extend the *TeamMember* and override the default *toString()* method.

* *Admin* — a team member class representing the admin role.

![admin.dart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/93lyn8btuil7ou985vta.png)

* *Developer* — a team member class representing the developer role.

![developer.dart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4cu1gz80nlts1l37x4ia.png)

* *Tester* — a team member class representing the tester (QA) role.

![tester.dart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9fq2kor72br8xvoczdr.png)

### NotificationHub

An abstract class that defines abstract methods to be implemented by specific notification hub classes. Method *getTeamMembers()* returns a list of registered team members to the hub, *register()* registers a new member to the hub. Method *send()* sends the notification to all the registered team members to the hub (excluding sender) while *sendTo<T>()* sends the notification to team members of a specific type (excluding sender).

![notification_hub.dart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yicpsy1t4t4f8or2806n.png)

### TeamNotificationHub

A specific notification hub implementing abstract *NotificationHub* methods. The class also contains private *teamMembers* property — a list of registered team members to the hub.

![team_notification_hub.dart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn3hqvf4s2h6br3k2ibd.png)

### Example

First of all, a markdown file is prepared and provided as a pattern’s description:

![Mediator Markdown](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jrv040a5azyvttf4sztd.gif)

The *MediatorExample* widget initialises the *TeamNotificationHub* and later uses it to send notifications between team members.

![mediator_example.dart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kdt329ml5jy5n6oi20ww.png)

Specific team members do not contain any reference about the others, they are completely decoupled. For communication, the notification hub is used that handles all the necessary logic to send and receive notifications from the team.

![Mediator Example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1f5ysq7i0uuys1d16qn0.gif)

As you can see in the example, you could send notifications from different team members, add new members later to the hub so they will be notified, too.

All of the code changes for the Mediator design pattern and its example implementation could be found [here](https://github.com/mkobuolys/flutter-design-patterns/pull/28).

## Your Contribution

💖 or 🦄 this article to show your support and motivate me to write better!
💬 Leave a response to this article by providing your insights, comments or wishes for the next topic.
📢 Share this article with your friends, colleagues on social media.
➕ Follow me on dev.to or any other social media platform.
⭐ Star the [Github](https://github.com/mkobuolys/flutter-design-patterns) repository.