Understanding the Mediator Pattern

The Mediator pattern is a behavioral design pattern that defines an object that encapsulates how a set of objects interact. By promoting loose coupling by keeping objects from referring to each other explicitly, it enables easier maintenance and extension of object interactions.

Problem

Imagine you're developing a complex user interface with many interrelated components, such as buttons, text fields, checkboxes, and so on. Here are some common challenges:

  • Every component needs to communicate with other components, creating complex dependencies.
  • Components are tightly coupled, making it difficult to reuse them in other contexts.
  • Changes to one component often require changes to many others.
  • The business logic is scattered across multiple components, making it hard to maintain.

Solution

The Mediator pattern suggests creating a mediator object that encapsulates all the interconnections between components. Instead of components communicating directly, they notify the mediator, which then communicates with the appropriate components.

Benefits of this approach include:

  • Components only know about the mediator, not about each other, reducing coupling.
  • Complex interaction logic is centralized in the mediator, making it easier to understand and maintain.
  • Components can be reused in different contexts with different mediators.
  • New components can be added without changing existing ones.

Structure

Mediator Pattern Structure

Participants

  • Mediator: Defines the interface for communication between components.
  • Concrete Mediator: Implements the Mediator interface and coordinates communication between components. It typically maintains references to all components.
  • Colleague: Defines the interface for components that communicate through the mediator.
  • Concrete Colleagues: Implement the Colleague interface and communicate with other components through the mediator.

When to Use

Use the Mediator Pattern when:

  • A set of objects communicate in well-defined but complex ways, resulting in interdependencies that are hard to understand.
  • Reusing an object is difficult because it refers to and communicates with many other objects.
  • Behavior that's distributed among several classes should be customizable without a lot of subclassing.
  • You want to avoid tight coupling between many related objects.

Benefits

  • Reduced Coupling: Components don't need to know about each other, only about the mediator.
  • Centralized Control: The interaction logic is contained in one place, the mediator.
  • Simplified Object Protocols: Reduces the number of relationships between objects from many-to-many to many-to-one.
  • Easier Maintenance: Changes to interaction logic are centralized in the mediator.

Real-World Uses

  • UI Development: Coordinating interactions between UI components like form elements.
  • Air Traffic Control: Coordinating communication between planes (colleagues) through the control tower (mediator).
  • Chat Applications: Managing communication between users through a chat server.
  • Event Bus Systems: Facilitating communication between decoupled components through events.
  • Middleware: Coordinating communication between different services in a distributed system.

Implementation Example

Here's a JavaScript implementation of the Mediator pattern that demonstrates a smart home system where various devices communicate through a central mediator:

// Mediator interface
class SmartHomeMediator {
    notify(sender, event) {}
}

// Concrete Mediator
class SmartHomeController extends SmartHomeMediator {
    constructor() {
        super();
        this.devices = {};
    }
    
    registerDevice(deviceName, device) {
        this.devices[deviceName] = device;
        device.setMediator(this);
    }
    
    notify(sender, event) {
        console.log(`Mediator: Handling '${event}' event from ${sender.name}`);
        
        if (sender instanceof MotionSensor && event === 'motion_detected') {
            // Turn on lights when motion is detected if it's dark
            if (this.devices['light_sensor'].currentLightLevel < 20) {
                this.devices['living_room_light'].turnOn();
                this.devices['hallway_light'].turnOn();
            }
        }
        
        if (sender instanceof LightSensor && event === 'light_changed') {
            // Adjust blinds based on light level
            if (sender.currentLightLevel > 80) {
                this.devices['living_room_blinds'].close();
            } else if (sender.currentLightLevel < 20) {
                this.devices['living_room_blinds'].open();
            }
        }
        
        if (sender instanceof Thermostat && event === 'temperature_changed') {
            // Adjust AC or heater based on temperature
            if (sender.currentTemperature > 25) {
                this.devices['air_conditioner'].turnOn();
                this.devices['heater'].turnOff();
            } else if (sender.currentTemperature < 18) {
                this.devices['heater'].turnOn();
                this.devices['air_conditioner'].turnOff();
            } else {
                this.devices['air_conditioner'].turnOff();
                this.devices['heater'].turnOff();
            }
        }
        
        if (event === 'leaving_home') {
            // Secure the house when leaving
            Object.values(this.devices).forEach(device => {
                if (device instanceof Light) {
                    device.turnOff();
                }
                if (device instanceof AirConditioner) {
                    device.turnOff();
                }
                if (device instanceof Heater) {
                    device.turnOff();
                }
            });
            this.devices['security_system'].arm();
        }
        
        if (event === 'returning_home') {
            // Welcome back mode
            this.devices['security_system'].disarm();
            if (this.devices['light_sensor'].currentLightLevel < 50) {
                this.devices['living_room_light'].turnOn();
                this.devices['hallway_light'].turnOn();
            }
        }
    }
}

// Base Colleague class
class SmartDevice {
    constructor(name) {
        this.name = name;
        this.mediator = null;
    }
    
    setMediator(mediator) {
        this.mediator = mediator;
    }
}

// Concrete Colleagues
class Light extends SmartDevice {
    constructor(name) {
        super(name);
        this.isOn = false;
    }
    
    turnOn() {
        if (!this.isOn) {
            this.isOn = true;
            console.log(`${this.name}: Light turned ON`);
        }
    }
    
    turnOff() {
        if (this.isOn) {
            this.isOn = false;
            console.log(`${this.name}: Light turned OFF`);
        }
    }
    
    toggle() {
        this.isOn ? this.turnOff() : this.turnOn();
        this.mediator.notify(this, this.isOn ? 'light_on' : 'light_off');
    }
}

class MotionSensor extends SmartDevice {
    constructor(name) {
        super(name);
    }
    
    detectMotion() {
        console.log(`${this.name}: Motion detected!`);
        this.mediator.notify(this, 'motion_detected');
    }
}

class LightSensor extends SmartDevice {
    constructor(name) {
        super(name);
        this.currentLightLevel = 50; // default 0-100
    }
    
    setLightLevel(level) {
        this.currentLightLevel = level;
        console.log(`${this.name}: Light level set to ${level}`);
        this.mediator.notify(this, 'light_changed');
    }
}

class Thermostat extends SmartDevice {
    constructor(name) {
        super(name);
        this.currentTemperature = 22; // default in Celsius
    }
    
    setTemperature(temperature) {
        this.currentTemperature = temperature;
        console.log(`${this.name}: Temperature set to ${temperature}°C`);
        this.mediator.notify(this, 'temperature_changed');
    }
}

class AirConditioner extends SmartDevice {
    constructor(name) {
        super(name);
        this.isOn = false;
    }
    
    turnOn() {
        if (!this.isOn) {
            this.isOn = true;
            console.log(`${this.name}: AC turned ON`);
        }
    }
    
    turnOff() {
        if (this.isOn) {
            this.isOn = false;
            console.log(`${this.name}: AC turned OFF`);
        }
    }
}

class Heater extends SmartDevice {
    constructor(name) {
        super(name);
        this.isOn = false;
    }
    
    turnOn() {
        if (!this.isOn) {
            this.isOn = true;
            console.log(`${this.name}: Heater turned ON`);
        }
    }
    
    turnOff() {
        if (this.isOn) {
            this.isOn = false;
            console.log(`${this.name}: Heater turned OFF`);
        }
    }
}

class Blinds extends SmartDevice {
    constructor(name) {
        super(name);
        this.isOpen = true;
    }
    
    open() {
        if (!this.isOpen) {
            this.isOpen = true;
            console.log(`${this.name}: Blinds OPENED`);
        }
    }
    
    close() {
        if (this.isOpen) {
            this.isOpen = false;
            console.log(`${this.name}: Blinds CLOSED`);
        }
    }
}

class SecuritySystem extends SmartDevice {
    constructor(name) {
        super(name);
        this.isArmed = false;
    }
    
    arm() {
        if (!this.isArmed) {
            this.isArmed = true;
            console.log(`${this.name}: Security system ARMED`);
        }
    }
    
    disarm() {
        if (this.isArmed) {
            this.isArmed = false;
            console.log(`${this.name}: Security system DISARMED`);
        }
    }
    
    triggerAlarm() {
        if (this.isArmed) {
            console.log(`${this.name}: 🚨 ALARM TRIGGERED! 🚨`);
            this.mediator.notify(this, 'alarm_triggered');
        }
    }
}

// Client code
function clientDemo() {
    const mediator = new SmartHomeController();
    
    // Create devices
    const livingRoomLight = new Light("Living Room Light");
    const hallwayLight = new Light("Hallway Light");
    const motionSensor = new MotionSensor("Motion Sensor");
    const lightSensor = new LightSensor("Light Sensor");
    const thermostat = new Thermostat("Thermostat");
    const ac = new AirConditioner("Air Conditioner");
    const heater = new Heater("Heater");
    const blinds = new Blinds("Living Room Blinds");
    const securitySystem = new SecuritySystem("Security System");
    
    // Register all devices with the mediator
    mediator.registerDevice('living_room_light', livingRoomLight);
    mediator.registerDevice('hallway_light', hallwayLight);
    mediator.registerDevice('motion_sensor', motionSensor);
    mediator.registerDevice('light_sensor', lightSensor);
    mediator.registerDevice('thermostat', thermostat);
    mediator.registerDevice('air_conditioner', ac);
    mediator.registerDevice('heater', heater);
    mediator.registerDevice('living_room_blinds', blinds);
    mediator.registerDevice('security_system', securitySystem);
    
    // Simulate different scenarios
    console.log("=== Evening Scenario: Motion Detected ===");
    lightSensor.setLightLevel(10); // Dark outside
    motionSensor.detectMotion();
    
    console.log("\n=== Hot Day Scenario ===");
    lightSensor.setLightLevel(90); // Bright sunny day
    thermostat.setTemperature(28); // It's hot
    
    console.log("\n=== Leaving Home Scenario ===");
    mediator.notify({ name: "Mobile App" }, "leaving_home");
    
    console.log("\n=== Returning Home Scenario ===");
    mediator.notify({ name: "Mobile App" }, "returning_home");
    
    console.log("\n=== Cold Evening Scenario ===");
    lightSensor.setLightLevel(5); // Very dark outside
    thermostat.setTemperature(16); // It's cold
}

// Run the demo
clientDemo();

In this example:

  • The SmartHomeMediator defines the interface for the mediator.
  • The SmartHomeController is the concrete mediator that coordinates all communication between smart devices.
  • The SmartDevice is the base colleague class that all devices inherit from.
  • Various concrete colleagues (Light, MotionSensor, etc.) interact through the mediator rather than directly with each other.
  • Devices notify the mediator of events (like motion detection or temperature changes), and the mediator determines how other devices should respond.

This design decouples the devices from each other, centralizing all coordination logic in the mediator. This makes the system much easier to maintain and extend - adding a new device or changing the behavior of existing devices requires changes only to the mediator, not to the devices themselves.

Interactive Demo

Experience the Mediator pattern in action with this interactive smart home simulation. Try triggering different events and see how the mediator coordinates the responses between devices.

Interactive Smart Home System

Control various smart devices and see how they communicate through the mediator.

Sensors

📡
Motion Sensor
☀️
Light Sensor
🌡️
Thermostat

Lights

💡
Living Room Light
OFF
💡
Hallway Light
OFF

Climate & Security

❄️
Air Conditioner
OFF
🔥
Heater
OFF
🪟
Blinds
OPEN
🔒
Security System
DISARMED

Scenarios

Smart Home System initialized. Try changing settings or triggering events!