Behavioral

Mediator

Define an object that encapsulates how a set of objects interact — promote loose coupling by preventing direct references between objects.

Intermediate Pattern #17 of 23

What is the Mediator Pattern?

Mediator centralises complex communications and control logic between related objects (colleagues). Instead of objects referencing each other directly, they all communicate through the mediator. This reduces the number of connections from O(N²) to O(N).

Without Mediator: N objects communicating directly = N×(N-1)/2 connections. With Mediator: each object has exactly one connection — to the mediator.

Real-world analogy: Air traffic control (ATC). Planes don't communicate directly with each other — they all talk to the ATC tower. The tower coordinates all movements. Remove the tower and every plane would need to talk to every other plane.

The Problem It Solves

You have a dialog form with many UI components — a TextBox, a CheckBox, a SubmitButton, a ListBox. When the TextBox changes, the SubmitButton should enable/disable. When the CheckBox is ticked, the ListBox should hide. Every component knows about every other — a tightly coupled web.

Mediator (the Dialog itself) receives notifications from all components and decides what to do. Each component only knows the mediator — zero knowledge of siblings.

Participants

Mediator (interface)
Declares the communication interface used by colleagues: typically notify(sender, event).
ConcreteMediator
Implements the coordination logic. Knows about all colleague objects. Responds to colleague events and triggers appropriate actions on other colleagues.
Colleague (Component)
Each component has a reference to the Mediator. Notifies the mediator when something happens. Never calls other colleagues directly.

Visual Flow Diagram

Mediator notify(sender, event) coordinates all TextBox CheckBox SubmitButton ListBox notify() notify() notify() notify() Without Mediator: N×(N-1)/2 = 6 direct connections for 4 objects → With Mediator: 4 connections total

Java Code Example

Java — Dialog Form Mediator
// Mediator interface
public interface Mediator {
    void notify(Component sender, String event);
}

// Base Colleague — knows only about the mediator
public abstract class Component {
    protected Mediator mediator;

    public Component(Mediator m) { this.mediator = m; }

    public void changed(String event) {
        mediator.notify(this, event); // all colleagues talk only to mediator
    }
}

// Concrete Colleagues
public class TextBox extends Component {
    private String text = "";
    public TextBox(Mediator m) { super(m); }

    public void setText(String t) {
        text = t;
        changed("textChanged");
    }
    public String getText() { return text; }
}

public class CheckBox extends Component {
    private boolean checked = false;
    public CheckBox(Mediator m) { super(m); }

    public void toggle() {
        checked = !checked;
        changed("toggled");
    }
    public boolean isChecked() { return checked; }
}

public class Button extends Component {
    private boolean enabled = false;
    public Button(Mediator m) { super(m); }
    public void setEnabled(boolean e) { enabled = e; System.out.println("Button " + (e ? "enabled" : "disabled")); }
    public boolean isEnabled() { return enabled; }
}

// Concrete Mediator — all coordination logic here
public class DialogMediator implements Mediator {
    public TextBox  loginInput;
    public CheckBox rememberMe;
    public Button   submitBtn;

    public void notify(Component sender, String event) {
        if (sender == loginInput && event.equals("textChanged")) {
            // Enable submit only when text is not empty
            submitBtn.setEnabled(!loginInput.getText().isBlank());
        }
        if (sender == rememberMe && event.equals("toggled")) {
            // Log the preference
            System.out.println("Remember me: " + rememberMe.isChecked());
        }
    }
}

// Client — wires everything
public class Main {
    public static void main(String[] args) {
        DialogMediator dialog = new DialogMediator();

        dialog.loginInput = new TextBox(dialog);
        dialog.rememberMe = new CheckBox(dialog);
        dialog.submitBtn  = new Button(dialog);

        dialog.loginInput.setText("user@example.com"); // Button enabled
        dialog.loginInput.setText("");                   // Button disabled
        dialog.rememberMe.toggle();                       // Remember me: true
    }
}

When to Use / Avoid

✓ Use When

  • Many objects communicate in complex ways causing tight coupling
  • An object references and depends on many other objects
  • Reusing a component in a different context is difficult due to its many dependencies
  • UI form coordination, chat rooms, event buses, workflow engines

✕ Avoid When

  • Only a few objects interact — a mediator adds unnecessary indirection
  • The mediator itself becomes a God Object with too much logic
  • Objects need direct, high-performance communication without routing overhead

Real-World Examples

Pros & Cons

Pros

  • Single Responsibility — interaction logic in one place
  • Open/Closed — add new colleagues without changing existing ones
  • Reduces coupling from O(N²) to O(N)
  • Colleagues become independently reusable

Cons

  • Mediator can become a God Object — all knowledge centralised
  • Single point of failure — mediator breakdown affects all colleagues
  • Can be harder to trace which colleagues affect which

How Mediator Can Be Broken

⚠ Attack Vectors

  • God Mediator: All business logic moves into notify() — it becomes a massive if-else chain handling every possible event from every colleague, defeating the purpose of separation
  • Colleagues referencing each other directly: A colleague bypasses the mediator and calls another colleague directly — the coupling Mediator was meant to eliminate creeps back in
  • Infinite notification loop: Colleague A notifies mediator → mediator updates Colleague B → B fires its own change event → mediator updates A → infinite loop
  • Mediator holds stale colleague references: A colleague is removed/destroyed but mediator still holds a reference — sends notifications to a dead object causing NPE or unexpected behaviour

✓ Prevention

  • Keep mediator thin: Delegate complex business rules to domain services — mediator only routes events and triggers colleague actions, it does not implement business logic itself
  • Strict rule — colleagues only call mediator: Enforce via code review or ArchUnit tests that no colleague class imports or references another colleague class directly
  • Re-entrancy guard: Track whether the mediator is already processing a notification — if so, queue the new notification rather than re-entering the handler recursively
  • Weak references or deregistration: Allow colleagues to deregister from the mediator. Use weak references in the mediator's colleague list so garbage-collected colleagues are not notified
Java — Break & Fix
// ❌ BREAKING — infinite loop: A notifies → mediator updates B → B fires → loop
public void notify(Component sender, String event) {
    if (sender == checkBox) {
        textBox.setText("auto-filled"); // setText fires "textChanged"
        // → mediator.notify(textBox, "textChanged")
        // → which might update checkBox → infinite loop!
    }
}

// ✅ FIX — re-entrancy guard
private boolean handling = false;

public void notify(Component sender, String event) {
    if (handling) return; // ✅ prevent re-entrant notification
    handling = true;
    try {
        if (sender == checkBox) {
            textBox.setText("auto-filled"); // won't re-enter
        }
    } finally {
        handling = false;
    }
}

// ❌ BREAKING — colleague bypasses mediator
public class CheckBox extends Component {
    private Button submitBtn; // ❌ direct reference to another colleague!

    public void toggle() {
        submitBtn.setEnabled(checked); // ❌ direct call — bypasses mediator
    }
}

// ✅ FIX — only notify mediator, never reference other colleagues
public class CheckBox extends Component {
    public void toggle() {
        checked = !checked;
        changed("toggled"); // ✅ only calls mediator.notify(this, "toggled")
    }
}

How Other Patterns Relate

Interview Cheat Sheet

  1. What: Central coordinator. Colleagues only talk to the mediator — never to each other. Reduces N×(N-1)/2 connections to N. All coordination logic lives in one place.
  2. How: Mediator interface with notify(sender, event). ConcreteMediator holds references to all colleagues. Each colleague holds a mediator reference and calls mediator.notify(this, event) on change.
  3. Key distinction from Observer: Observer = one-to-many broadcast (subject to subscribers). Mediator = many-to-many coordination (any colleague to any colleague via mediator). Observer is decentralised; Mediator is centralised.