Java programming language logo

The Mediator Design Pattern

The Mediator pattern helps the communication between different type of objects while keeping them decoupled from each other. In this pattern, the objects don’t communicate directly with each other, instead of it introduces a mediator object that stands between them and controls all communication for them.

So Mediator pattern is used when objects have to communicate with each other. Without the pattern every object should have references to the others and call their methods directly. With this pattern, every object only has a reference to the mediator object, and when communication is needed, only the mediator is notified, and that calls the appropriate methods of the other objects. So every object only communicates with the mediator. This makes the objects simpler, only the mediator class contains the logic that belongs to communication and knows the details of the other objects in the system.

Objects are loosely coupled in the Mediator pattern. Every object depends only on the mediator. This reduces the complexity of the system and leads to a more maintainable and flexible design.

Mediator is a behavioral pattern.

Example

Let’s see a Java example for the Mediator pattern.

The next example is about a home where different devices work together. The owner of this home has very sophisticated demands how the devices have to cooperate. For example, when alarm of the flat is activated that means nobody is in the flat, then the Hi-Fi and the mood lighting should be get turned off. At the same time, this is the best time for the robot vacuum cleaner to do its job, so it should clean if it is time to do.

Four devices occur in this example: the home alarm, the Hi-Fi, the mood lighting and the robot vacuum cleaner. The mediator class makes them work together.

Let’s start with the HomeAlarm class.

It receives the Mediator instance in its constructor and stores it in the mediator field, and finally sets itself into the mediator by the setComponent method. So the HomeAlarm class has a reference to the Mediator class and vice versa.

The activate and deactivate methods work in the same way. They not only activate and deactivate the alarm, but they notify the mediator about it by calling its triggerEvent method.


package com.programcodex.designpatterns.mediator;

public class HomeAlarm {

    private Mediator mediator;
    
    public HomeAlarm(Mediator mediator) {
        this.mediator = mediator;
        this.mediator.setComponent(this);
    }

    public void activate() {
        System.out.println("Home alarm system is activated.");
        mediator.triggerEvent(Event.HOME_ALARM_ON);
    }
    
    public void deactivate() {
        System.out.println("Home alarm system is deactivated.");
        mediator.triggerEvent(Event.HOME_ALARM_OFF);
    }
}

The Event enum contains all event types those occur in this example. The HOME_ALARM_ON and HOME_ALARM_OFF events were triggered in the previous class.


package com.programcodex.designpatterns.mediator;

public enum Event {
    HOME_ALARM_ON,
    HOME_ALARM_OFF,
    HIFI_ON,
    ROBOT_VACUUM_CLEANER_ON
}

The HiFi class has almost the same logic as the HomeAlarm. The on and off methods change the value of the isOn field and trigger an appropriate event in the mediator.


package com.programcodex.designpatterns.mediator;

public class HiFi {

    private Mediator mediator;
    private boolean isOn;
    
    public HiFi(Mediator mediator) {
        this.mediator = mediator;
        this.mediator.setComponent(this);
    }

    public void on() {
        if (!isOn) {
            isOn = true;
            System.out.println("Hi-Fi is on.");
            mediator.triggerEvent(Event.HIFI_ON);
        }
    }

    public void off() {
        if (isOn) {
            isOn = false;
            System.out.println("Hi-Fi is off.");
        }
    }
}

The MoodLighting class also very similar to the previous classes, but this one doesn’t stores a reference to the mediator and never triggers an event when changes state.

The HiFi class notifies mediator when it gets turned on, so the mediator can also turning on the mood lighting, but the reverse not working. The MoodLighting does not notifies the mediator when it gets turned on, so nothing else has a chance to react to it.


package com.programcodex.designpatterns.mediator;

public class MoodLighting {

    private boolean isOn;

    public MoodLighting(Mediator mediator) {
        mediator.setComponent(this);
    }

    public void turnOn() {
        if (!isOn) {
            isOn = true;
            System.out.println("Mood lighting is turned on.");
        }
    }
    
    public void turnOff() {
        if (isOn) {
            isOn = false;
            System.out.println("Mood lighting is turned off.");
        }
    }
}

The RobotVacuumCleaner can be started with the startCleaning method and stopped with the finishCleaning method. It notifies the mediator when it starts.

A good robot cleaner can be scheduled. To keep this example simple, this feature is represented by the isItTimeToClean boolean field. When it’s time to clean, the scheduler sets this to true. For the shake of simplicity, this is always true now.

The startCleaningIfNeeded method starts the cleaner if it has an unfinished cleaning, or it is time to do.


package com.programcodex.designpatterns.mediator;

public class RobotVacuumCleaner {

    private Mediator mediator;
    private boolean inProgress;
    private boolean isItTimeToClean = true;

    public RobotVacuumCleaner(Mediator mediator) {
        this.mediator = mediator;
        this.mediator.setComponent(this);
    }

    public void startCleaningIfNeeded() {
        System.out.println("Robot vacuum cleaner is allowed to work if needed.");
        if (isItTimeToClean) {
            startCleaning();
        }
    }

    public void startCleaning() {
        if (!inProgress) {
            inProgress = true;
            System.out.println("Robot vacuum cleaner is started.");
            mediator.triggerEvent(Event.ROBOT_VACUUM_CLEANER_ON);
        }
    }

    public void finishCleaning() {
        if (inProgress) {
            inProgress = false;
            System.out.print("Robot vacuum cleaner is finished.");
            System.out.println(" Going back to dock.");
        }
    }
}

The Mediator class is main point in this example. It contains all logic about how things have to work together. The triggerEvent method receives an event from a device and handles the other devices accordingly.

HOME_ALARM_ON event: this means no one is at home, so the Hi-Fi and the mood lighting should be turned off. At the same time, this is the best time for the robot vacuum cleaner to make noise.

HOME_ALARM_OFF event: this means somebody getting home and doesn’t want to hear the noise of the robot vacuum cleaner, so it has to interrupt its task.

HIFI_ON event: when the Hi-Fi getting turned on, the mood lighting also turns on to increase the mood. Obviously, when somebody wants to hear some music and relax, the robot cleaner should be get turned off.

ROBOT_VACUUM_CLEANER_ON event: when the robot starts to clean, it makes no sense to keep the Hi-Fi turned on. It ruins the music.

Finally, the setComponent methods are called from the constructors of the devices.


package com.programcodex.designpatterns.mediator;

public class Mediator {

    private HomeAlarm homeAlarm;
    private HiFi hifi;
    private MoodLighting moodLighting;
    private RobotVacuumCleaner robot;

    public void triggerEvent(Event event) {
        switch (event) {
            case HOME_ALARM_ON:
                hifi.off();
                moodLighting.turnOff();
                robot.startCleaningIfNeeded();
                break;
            case HOME_ALARM_OFF:
                robot.finishCleaning();
                break;
            case HIFI_ON:
                moodLighting.turnOn();
                robot.finishCleaning();
                break;
            case ROBOT_VACUUM_CLEANER_ON:
                hifi.off();
                break;
        }
    }    

    public void setComponent(HomeAlarm homeAlarm) {
        this.homeAlarm = homeAlarm;
    }

    public void setComponent(HiFi hifi) {
        this.hifi = hifi;
    }

    public void setComponent(MoodLighting moodLighting) {
        this.moodLighting = moodLighting;
    }

    public void setComponent(RobotVacuumCleaner robot) {
        this.robot = robot;
    }
}

And we are done. It’s time to run these things.

The devices are used in the main method. Some of them are turned on twice, and the two cases produce different effects to the other devices. For example, when the Hi-Fi is turned on again the mood lighting has already been on, so it cannot be turned on again. This causes the differences.


package com.programcodex.designpatterns.mediator;

public class TestMediator {

    public static void main(String[] args) {
        Mediator mediator = new Mediator();

        HomeAlarm homeAlarm = new HomeAlarm(mediator);
        HiFi hifi = new HiFi(mediator);
        MoodLighting moodLighting = new MoodLighting(mediator);
        RobotVacuumCleaner robot = new RobotVacuumCleaner(mediator);

        System.out.println("\n### Let's turn on the Hi-Fi. ###");
        hifi.on();
        
        System.out.println("\n### Let's start the robot cleaner. ###");
        robot.startCleaning();

        System.out.println("\n### Let's turn on the Hi-Fi again. ###");
        hifi.on();

        System.out.println("\n### Let's activate the home alarm. ###");
        homeAlarm.activate();

        System.out.println("\n### Let's deactivate the home alarm. ###");
        homeAlarm.deactivate();

        System.out.println("\n### Let's activate the home alarm again. ###");
        homeAlarm.activate();
    }
}

The output of the above test class:

The Mediator Design Pattern