Mediator is a behavioral design pattern which is used to define how objects interact with one another without explicitly referring them. It promotes loose coupling between objects by introducing a mediator object that acts as an intermediary for communication between them.

What is Mediator Design Pattern?

In this pattern, a central object coordinates the activities of many other objects.

Mediator discourages direct communication between two objects because it leads to tight coupling. Instead, it promotes having a mediator which faciliates communication between these two objects. For example, in a library, each account holder do not communicate and pass books to each other. That would become very difficult. Instead, each library account holder communicates through librarian who act as a mediator. The same thing happens in business as well. If a team needs help with something, they communicate the requirement to manager and the manager passes the request to individual developer. If there was no manager acting as a mediator, it will be chaotic and developer would be swamped by requests from multiple different teams.

This pattern is useful when you need to decouple two objects. This improves maintainability and reuse of those classes. This is used where a well-defined objects need to collaborate in a complex way. This acts as a hub router in which all communication to each network connected device is routed through the hub router. The router connects various devices like printer, laptop, drives to communicate with one another.

How to implement Mediator Pattern?

The mediator pattern consists of following:

  • Mediator: This is an interface which declares methods for communication between different component objects. We will also have concrete implementataion of mediator objects implementing this interface. These concrete mediators encapsulate relationship between different components. These concrete mediators can keep references of all components they manage.
  • Component: These are different classes which have bussiness logic. Components do not need to be aware of other components. If a component need to communicate with other component, it notifies the mediator and based on the sender of the notification, the mediator knows whom to pass the response. The sender does not need to know who will handle the request submitted by it and similarly the receiver of the request from the mediator does not know who sent the request.

Hub router Example Code

  1. Identify the two objects which are tightly-connected and they include collaboration logic along with their business logic. In this case, we have a Computer and Printer which might be tightly coupled and we want to faciliate their collaboration using a mediator. So, declare methods for collaboration between Printer and Computer in Mediator interface.
1public interface Mediator {
2    void register(Device device);
3    void send(Device sender, String message);
4}
  1. Implement this interface using HubRouter through which each of these connected devices communicate. This mediator includes references to all devices it manages.
 1public class HubRouter implements Mediator {
 2    private List<Device> devices;
 3
 4    public HubRouter() {
 5        this.devices = new ArrayList<>();
 6    }
 7
 8    @Override
 9    public void register(Device device) {
10        devices.add(device);
11    }
12
13    @Override
14    public void send(Device sender, String message) {
15        for (Device device: devices) {
16            if (device != sender) {
17                device.receive(message);
18            }
19        }
20    }
21}
  1. Now, our components Printer and Computer have a reference to Mediator. Notice that when a send() operation is done from Computer, it passes through Mediator.
 1public class Computer implements Device{
 2    private final String name;
 3    private final Mediator mediator;
 4
 5    public Computer(String name, Mediator mediator) {
 6        this.name = name;
 7        this.mediator = mediator;
 8    }
 9
10    @Override
11    public String getName() {
12        return name;
13    }
14
15    @Override
16    public void send(String message) {
17        System.out.println(name + " sends: " + message);
18        mediator.send(this, message);
19    }
20
21    @Override
22    public void receive(String message) {
23        System.out.println(name + " received: " + message);
24    }
25
26    public void browse(String url) {
27        System.out.println("Browsing url : " + url);
28    }
29
30    public void download(String file) {
31        System.out.println("Downloading file : " + file);
32    }
33}
  1. Similarly, for Printer once printing is done, message is sent using Mediator.
 1public class Printer implements Device {
 2    private final String name;
 3    private final Mediator mediator;
 4
 5    public Printer(String name, Mediator mediator) {
 6        this.name = name;
 7        this.mediator = mediator;
 8    }
 9
10    @Override
11    public String getName() {
12        return name;
13    }
14
15    @Override
16    public void send(String message) {
17        System.out.println(name + " sends: " + message);
18        mediator.send(this, message);
19    }
20
21    @Override
22    public void receive(String message) {
23        System.out.println(name + " received: " + message);
24    }
25
26    public void print(String file) {
27        System.out.println("Printing file : " + file);
28    }
29}
  1. The component do not have any reference to the other component. Each component register with the mediator to collaborate with each other.
 1public class ClientMain {
 2    public static void main(String[] args) {
 3        Mediator mediator = new HubRouter();
 4        Device computer = new Computer("Dell Laptop", mediator);
 5        Device printer = new Printer("HP Printer", mediator);
 6
 7        mediator.register(computer);
 8        mediator.register(printer);
 9
10        computer.send("Hello, Print this document");
11        printer.send("Finished printing document");
12    }
13}

Lights example using Mediator and Command pattern

Usually, mediator pattern can be combined with command pattern.

We have set of lights we want to control.

1public interface Light {
2    void turnOn();
3    void turnOff();
4}
 1public class BedroomLight implements Light {
 2    private boolean on;
 3
 4    @Override
 5    public void turnOn() {
 6        on = true;
 7        System.out.println("Bedroom light is on");
 8    }
 9
10    @Override
11    public void turnOff() {
12        on = false;
13        System.out.println("Bedroom light is off");
14    }
15
16    public boolean isOn() {
17        return on;
18    }
19}
 1public class KitchenLight implements Light {
 2    private boolean on;
 3
 4    @Override
 5    public void turnOn() {
 6        on = true;
 7        System.out.println("Kitchen light is on");
 8    }
 9
10    @Override
11    public void turnOff() {
12        on = false;
13        System.out.println("Kitchen light is off");
14    }
15
16    public boolean isOn() {
17        return on;
18    }
19}

For this, we can define different on and off commands.

1public interface Command {
2    void execute();
3}
 1public class OffCommand implements Command {
 2    private final Light light;
 3
 4    public OffCommand(Light light) {
 5        this.light = light;
 6    }
 7
 8    @Override
 9    public void execute() {
10        light.turnOff();
11    }
12}
 1public class OnCommand implements Command {
 2    private final Light light;
 3
 4    public OnCommand(Light light) {
 5        this.light = light;
 6    }
 7
 8    @Override
 9    public void execute() {
10        light.turnOn();
11    }
12}

Now, we can use the mediator to collaborate between light and the command. This way we can reuse the same command or even lights in other application.

1public interface MediatorController {
2    void registerLight(String name, Light light);
3    void sendCommand(String name, Command command);
4}
 1public class LightController implements MediatorController {
 2    Map<String, Light> lights;
 3
 4    public LightController() {
 5        this.lights = new HashMap<>();
 6    }
 7
 8    @Override
 9    public void registerLight(String name, Light light) {
10        lights.put(name, light);
11    }
12
13    @Override
14    public void sendCommand(String name, Command command) {
15        Light light = lights.get(name);
16        if (light != null) {
17            command.execute();
18        } else {
19            throw new IllegalArgumentException("Light not found");
20        }
21    }
22}

From client’s perspective, there is no coupling between these two.

 1public class Main {
 2    public static void main(String[] args) {
 3        MediatorController lightMediator = new LightController();
 4        Light bedroomLight = new BedroomLight();
 5        Light kitchenLight = new KitchenLight();
 6        lightMediator.registerLight("Bedroom", kitchenLight);
 7        lightMediator.registerLight("Kitchen", bedroomLight);
 8
 9        lightMediator.sendCommand("Bedroom", new OnCommand(bedroomLight));
10        lightMediator.sendCommand("Kitchen", new OnCommand(kitchenLight));
11        lightMediator.sendCommand("Bedroom", new OffCommand(bedroomLight));
12    }
13}

Advantages:

  • This pattern is used to reduce coupling between two collaborating objects. Thus, it makes code more reusable as well as testable.
  • Mediator pattern improves maintainability of the code. We can easily add mediator to facilitate collaboration between well-defined objects.
  • Mediator promotes single responsibility principle. It separates the communication concerns from the object’s core functionalities.

Disadvantages:

  • The mediator object may add extra layer of complexity to the system.
  • If the mediator fails, all communication between objects will break down. This makes mediator single point of failure.

Comparison with Observer Pattern

Mediator PatternObserver Pattern
used to define how objects interact with one anotherThis is more like obe to many broadcast message
It helps decouple objects by eliminating their direct references.This focuses on object decoupling in different way
It is more specific.It makes a more generic messaging (becoming listener)

Summary

  • Mediator is a useful pattern when we want to have loose coupling by simplifying collaboration between two tightly coupled objects.
  • This pattern can be used with command pattern.