Skip to main content

Command Palette

Search for a command to run...

Mediator Pattern

Updated
7 min read
Mediator Pattern

The Mediator Pattern is a behavioral design pattern that centralizes communication between objects. Instead of objects talking to each other directly (which creates a tangled web of dependencies), they communicate through a mediator that coordinates interactions.

Think of an airport control tower. Pilots don’t talk directly to every other plane—they talk to the control tower, which manages traffic safely. That’s the Mediator Pattern in action.

The Problem

In a complex UI system, components often talk to each other directly:

// Without Mediator Pattern - tightly coupled components
class LoginForm {
    private Button loginButton;
    private TextBox username;
    private TextBox password;
    private Checkbox rememberMe;

    public LoginForm() {
        loginButton = new Button();
        username = new TextBox();
        password = new TextBox();
        rememberMe = new Checkbox();

        username.setOnChange(() -> validate());
        password.setOnChange(() -> validate());
        rememberMe.setOnChange(() -> toggleRememberMe());
    }

    private void validate() {
        if (username.getText().isEmpty() || password.getText().isEmpty()) {
            loginButton.disable();
        } else {
            loginButton.enable();
        }
    }

    private void toggleRememberMe() {
        // Direct cross-component logic
    }
}

Problems:

  • Tight coupling: Each component knows too much about others

  • Hard to reuse: Components are glued to specific screens

  • Difficult to maintain: Changes ripple across components

  • Complex dependencies: Many-to-many communication spaghetti

The Solution: Mediator Pattern

The Mediator Pattern introduces a mediator that handles all communication.

](https://mermaid.live/edit#pako:eNqdlN2OojAUx1_FHG_cDBCgpUJjTAD1bl5gw00DVclAa0rdHdfx3Rf5ENSZzGZ7A-fj_zuH05YzpDLjQCEtWFWtcrZTrEzEpF6NZ_LKs5xpqSbn1ntdi0UuNFdblvLlcnC_CKnz7WlWcZFxZUz4Ly70jzZ-ScSYGkuRKq75Z3QzleVBilobfuaM_rNgJ7-rVHblR8iK676pWR_-BhmOmS-ZDGffCKIHQfQkuM1l8WFZT9Nqc0b1TfNjOdgP4eiL8MMOmOYoJ_yXpKjvttKngg89b_OioFOCCNk4RqWVfON0ihHy47Azzd95pvcUHd6NVBZS0emmWWPcU-0WG4VrslndsLbr43h-j3UHrB3jkKzvsf1BaHnhfINW9sDzAkKCr3kEr1H93WDATuUZUK2O3ICSq5JdTWg2NgG95yVPgNavGVNvCSTiUmsOTPyUsuxlSh53e6BbVlS1dTxkTPPuCt68qjnbsTwKDdRrEEDP8A7UdbHl-T5GbuD4c-I4gQEnoL5nEd8OiINcB3nExhcD_jRFbWvu1LmOj7HrIQ8bUI-2nuxr9we4Pi5_AQfvRmg align="center")

The pattern separates:

  • Who communicates (Components)

  • How they communicate (Mediator)

  • When they react (Mediator logic)

How It Solves Each Problem

Problem How Mediator Pattern Solves It
Tight coupling Components no longer reference each other directly—only the mediator.
Hard to reuse Components become reusable because they only depend on the mediator interface.
Difficult to maintain Communication rules are centralized in one place.
Complex dependencies Many-to-many communication is replaced by one-to-many.

Key Components

  1. Mediator: Interface defining communication rules

  2. ConcreteMediator: Implements coordination logic

  3. Component: Base class for UI elements or system modules

  4. ConcreteComponents: Actual interacting objects

How It Solves the Problem

Real-World Implementation

Example 1: Chat Room (Classic Mediator)

import java.util.ArrayList;
import java.util.List;

// Mediator Interface
interface ChatMediator {
    void sendMessage(String message, User user);
    void addUser(User user);
}

// Concrete Mediator
class ChatRoom implements ChatMediator {
    private List<User> users = new ArrayList<>();

    @Override
    public void addUser(User user) {
        users.add(user);
    }

    @Override
    public void sendMessage(String message, User sender) {
        for (User user : users) {
            if (user != sender) {
                user.receive(message, sender.getName());
            }
        }
    }
}

// Colleague
class User {
    private String name;
    private ChatMediator mediator;

    public User(String name, ChatMediator mediator) {
        this.name = name;
        this.mediator = mediator;
    }

    public void send(String message) {
        System.out.println(name + " sends: " + message);
        mediator.sendMessage(message, this);
    }

    public void receive(String message, String sender) {
        System.out.println(name + " receives from " + sender + ": " + message);
    }

    public String getName() {
        return name;
    }
}

// Client
public class ChatDemo {
    public static void main(String[] args) {
        ChatMediator chatRoom = new ChatRoom();

        User alice = new User("Alice", chatRoom);
        User bob = new User("Bob", chatRoom);
        User carl = new User("Carl", chatRoom);

        chatRoom.addUser(alice);
        chatRoom.addUser(bob);
        chatRoom.addUser(carl);

        alice.send("Hello everyone!");
        bob.send("Hey Alice!");
    }
}

Example 2: UI Form Mediator

import java.util.function.Consumer;

// Mediator Interface
interface FormMediator {
    void notify(Component component, String event);
}

// Base Component
abstract class Component {
    protected FormMediator mediator;

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

class Button extends Component {
    private boolean enabled = false;

    public Button(FormMediator mediator) {
        super(mediator);
    }

    public void click() {
        if (enabled) {
            System.out.println("Login clicked!");
        }
    }

    public void enable() {
        enabled = true;
        System.out.println("Button enabled");
    }

    public void disable() {
        enabled = false;
        System.out.println("Button disabled");
    }
}

class TextBox extends Component {
    private String text = "";

    public TextBox(FormMediator mediator) {
        super(mediator);
    }

    public void setText(String text) {
        this.text = text;
        mediator.notify(this, "textChanged");
    }

    public String getText() {
        return text;
    }
}

class LoginFormMediator implements FormMediator {
    private Button loginButton;
    private TextBox username;
    private TextBox password;

    public LoginFormMediator(Button loginButton, TextBox username, TextBox password) {
        this.loginButton = loginButton;
        this.username = username;
        this.password = password;
    }

    @Override
    public void notify(Component component, String event) {
        if (event.equals("textChanged")) {
            if (!username.getText().isEmpty() && !password.getText().isEmpty()) {
                loginButton.enable();
            } else {
                loginButton.disable();
            }
        }
    }
}

// Client
public class LoginDemo {
    public static void main(String[] args) {
        Button loginButton = new Button(null);
        TextBox username = new TextBox(null);
        TextBox password = new TextBox(null);

        LoginFormMediator mediator = new LoginFormMediator(loginButton, username, password);

        // Inject mediator
        loginButton.mediator = mediator;
        username.mediator = mediator;
        password.mediator = mediator;

        username.setText("alice");
        password.setText("");
        password.setText("secret");
    }
}

Example 3: Air Traffic Control

import java.util.ArrayList;
import java.util.List;

// Mediator
interface ControlTower {
    void requestLanding(Plane plane);
    void requestTakeoff(Plane plane);
    void addPlane(Plane plane);
}

class AirportControlTower implements ControlTower {
    private List<Plane> planes = new ArrayList<>();

    @Override
    public void addPlane(Plane plane) {
        planes.add(plane);
    }

    @Override
    public void requestLanding(Plane plane) {
        System.out.println("Tower: " + plane.getId() + " cleared to land");
    }

    @Override
    public void requestTakeoff(Plane plane) {
        System.out.println("Tower: " + plane.getId() + " cleared to takeoff");
    }
}

class Plane {
    private String id;
    private ControlTower tower;

    public Plane(String id, ControlTower tower) {
        this.id = id;
        this.tower = tower;
    }

    public void requestLanding() {
        tower.requestLanding(this);
    }

    public void requestTakeoff() {
        tower.requestTakeoff(this);
    }

    public String getId() {
        return id;
    }
}

// Client
public class AirportDemo {
    public static void main(String[] args) {
        ControlTower tower = new AirportControlTower();
        Plane p1 = new Plane("AA123", tower);
        Plane p2 = new Plane("BA456", tower);

        tower.addPlane(p1);
        tower.addPlane(p2);

        p1.requestLanding();
        p2.requestTakeoff();
    }
}

Workflow Diagram

Real-World Use Cases

  • GUI Systems: Dialog boxes, form validation, UI coordination

  • Chat Applications: User messaging via a central server

  • Air Traffic Control: Central control for planes

  • Workflow Engines: Coordinating tasks and processes

  • Game Development: Mediating between objects like player, NPCs, and UI

  • Microservices Gateways: Centralized API orchestration

  • Event Bus Systems: Mediator as an event dispatcher

When to Use the Mediator Pattern

✅ Use When

  1. Many-to-Many Communication: Components are overly connected

  2. Reusable Components: You want UI or modules to be reused

  3. Centralized Control: You want one place for interaction rules

  4. Complex Workflows: Many objects interact in complex ways

  5. Loose Coupling: Reduce dependencies between classes

❌ Avoid When

  1. Simple Systems: Direct calls are fine

  2. Single Interaction: No need for mediator

  3. Mediator Becomes God Object: Avoid putting too much logic in one mediator

Benefits

  1. Loose Coupling: Components depend only on mediator

  2. Single Responsibility: Each component focuses on its role

  3. Easier Maintenance: Changes centralized in mediator

  4. Reusable Components: Components are no longer context-specific

  5. Simpler Extensions: Add new components without breaking old ones

Drawbacks

  1. Mediator Complexity: Can become too large if overloaded

  2. Single Point of Failure: Mediator is critical for communication

  3. Hidden Flow: Debugging can be harder since communication is indirect

Mediator vs Observer Pattern

Aspect Mediator Observer
Purpose Central coordination Event-based notification
Coupling Reduced between colleagues Loose between subjects & observers
Communication Two-way, orchestrated One-way broadcasts
Control Centralized logic Decentralized reactions

Best Practices

  1. Keep Mediator Focused: Avoid a "god object"

  2. Use Interfaces: Depend on Mediator not concrete class

  3. Event-driven Updates: Use event types for clarity

  4. Test Mediator Logic: Central logic deserves tests

  5. Avoid Excessive Indirection: Don’t overuse for simple cases

Conclusion

The Mediator Pattern reduces chaos by centralizing communication. It shines when many components need to coordinate without tightly coupling themselves together.

Remember: Let the mediator handle the drama! 🎭


🎯 Key Takeaway

The Mediator Pattern is about centralizing communication. When too many objects talk to each other directly, introduce a mediator to keep the system sane.


Two components argued for hours until the Mediator stepped in and said, "Stop direct messaging—I'm literally here for this." 😄

Happy Mediating! 🎭✨