Skip to Content
Course content

293: The Mediator Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably run into this before: you start a project with three or four classes that need to talk to each other. It's simple enough. But then you add a fifth class, a sixth, and suddenly you have a "spaghetti" graph of dependencies. Every class knows about every other class. If you change a method signature in one, five other classes break. This is exactly the problem the Mediator pattern is designed to kill.

The core idea is simple: instead of objects talking to each other directly, they talk to a "Mediator" object. The mediator handles the coordination. The objects themselves stay blissfully ignorant of who else exists in the system.

The Chaos of Direct Communication

Let's imagine we're building a simple Smart Home system. We have a Light and an Alarm. In a naive implementation, if the alarm triggers, it needs to tell the light to flash. It looks like this:

class SmartLight {
    public void flash() { System.out.println("Lights flashing red!"); }
}

class SmartAlarm {
    private SmartLight light; // Direct dependency!

    public SmartAlarm(SmartLight light) { this.light = light; }

    public void trigger() {
        System.out.println("ALARM TRIGGERED!");
        light.flash(); 
    }
}

This works for two classes. But what happens when we add a SmartThermostat that needs to shut off the HVAC when the alarm goes off? Or a SmartLock that needs to unlock the doors? The SmartAlarm constructor becomes a nightmare of dependencies. We're creating a tight coupling that will make testing a headache.

Introducing the Central Hub

To fix this, we need a mediator. I like to think of this as the "Air Traffic Controller" of the software world. The planes (our devices) don't talk to each other; they only talk to the tower.

First, we define a HomeMediator interface. This allows us to swap out the logic of our hub without changing the devices themselves.

interface HomeMediator {
    void notify(Component component, String event);
}

abstract class Component {
    protected HomeMediator mediator;

    public Component(HomeMediator mediator) {
        this.mediator = mediator;
    }
}

Now, our devices just report "events" to the mediator. They don't care who is listening.

class SmartAlarm extends Component {
    public SmartAlarm(HomeMediator mediator) { super(mediator); }

    public void trigger() {
        System.out.println("Alarm: I've been triggered!");
        mediator.notify(this, "ALARM_ON");
    }
}

class SmartLight extends Component {
    public SmartLight(HomeMediator mediator) { super(mediator); }

    public void flash() { System.out.println("Light: Flashing red!"); }
}

Fixing my Concrete Dependency Mistake

Here is where I usually trip up when I'm rushing a build. I initially tried to make the ControlHub (the concrete mediator) use a generic List<Component> and cast them to specific types inside the notify method. It looked like this: if (component instanceof SmartLight) ....

I realized quickly that this is a "code smell." If I have to use instanceof and casting everywhere, I've just moved the spaghetti from the devices into the mediator. I've essentially created a "God Object" that knows too much. To fix this, I'll explicitly register the components the hub needs to manage. This keeps the logic clean and type-safe.

class ControlHub implements HomeMediator {
    private SmartAlarm alarm;
    private SmartLight light;

    public void setAlarm(SmartAlarm alarm) { this.alarm = alarm; }
    public void setLight(SmartLight light) { this.light = light; }

    @Override
    public void notify(Component component, String event) {
        if (component == alarm && event.equals("ALARM_ON")) {
            System.out.println("Hub: Alarm triggered! Coordinating response...");
            light.flash();
            // We could easily add thermostat.shutDown() here later
        }
    }
}

Putting the Hub to Work

Now, look at how the setup happens. The devices don't know about each other; they only know about the hub. The hub knows about the devices, but the devices are decoupled from one another.

public class Main {
    public static void main(String[] args) {
        ControlHub hub = new ControlHub();
        
        SmartAlarm alarm = new SmartAlarm(hub);
        SmartLight light = new SmartLight(hub);
        
        hub.setAlarm(alarm);
        hub.setLight(light);
        
        // The alarm triggers, the hub coordinates, the light flashes.
        alarm.trigger();
    }
}

If we decide that the lights should only flash if it's nighttime, we only change the logic in ControlHub. We don't touch a single line of code in the SmartAlarm or SmartLight classes. That's the power of the Mediator pattern: it encapsulates how a set of objects interact.




📋 Practical Task

Implementation: Airport Runway Control Tower

Build a small system simulating an airport runway. You need to prevent two planes from using the same runway at the same time.

  • Create a TowerMediator interface with a requestPermission(Plane plane, String action) method.
  • Implement a ControlTower class that tracks whether a runway is currently "occupied".
  • Create a Plane class that takes the mediator in its constructor.
  • The Plane should have a method requestLanding() which calls the mediator.
  • The ControlTower should only grant permission to land if the runway is clear. If it is occupied, it should tell the plane to "circle the airport".
  • Implement a requestTakeoff() method that clears the runway for other planes once the plane has successfully departed.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.