SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignMediator Pattern
Software DesignBehavioral Patterns

Mediator Pattern

Define a central object that encapsulates how a set of objects interact — reduce chaotic many-to-many dependencies.

July 24, 20245 min read
design-patternsjavamediatorbehavioral

One-line Definition

Encapsulates object interactions inside a central coordinator, replacing chaotic many-to-many dependencies with organized one-to-many relationships.


Problem

Suppose you are building a complex UI dialog with a checkbox, a text field, and a submit button. Checking the box enables the text field. Entering text enables the button. If the checkbox directly calls methods on the text field, and the text field directly calls the button, your UI components become incredibly tangled. Reusing that checkbox in another dialog becomes impossible. The Mediator Pattern solves this by moving all interaction logic into a central Coordinator (the Dialog), so components only talk to the Dialog, never to each other.


Real-world Analogy

At an airport, planes don't communicate directly with each other — that would be chaos. Instead, all planes talk to the control tower (mediator). The tower decides who can land, who should hold, and who should take off. Each pilot only knows the tower, not the other 50 planes in the sky.


Structure

  • Client
    • Sets up the Mediator and registers the Colleagues with it.
  • Mediator
    • Interface defining the communication methods (e.g., notify(), sendMessage()).
  • ConcreteMediator
    • Implements the coordination logic and holds references to all Colleagues.
  • Colleague
    • Each participant holds a reference to the Mediator and communicates strictly through it — never directly to other Colleagues.

Diagram

classDiagram
    class Client {
    }
    class Mediator {
        <<interface>>
        +notify(sender, event)
    }
    class ConcreteMediator {
        -Colleague1 c1
        -Colleague2 c2
        +notify(sender, event)
    }
    class Colleague {
        <<abstract>>
        #Mediator mediator
    }
    class Colleague1 {
        +doAction()
    }
    class Colleague2 {
        +doAction()
    }
    
    Client --> ConcreteMediator
    ConcreteMediator ..|> Mediator
    Colleague1 --|> Colleague
    Colleague2 --|> Colleague
    Colleague o-- Mediator : communicates through
    ConcreteMediator --> Colleague1 : coordinates
    ConcreteMediator --> Colleague2 : coordinates

Code Walkthrough

Notice how the User objects (Colleagues) never hold references to one another. When Alice sends a message, she sends it to the ChatRoom (Mediator), which handles distributing it to everyone else.

interface ChatMediator {
    void sendMessage(String message, User sender);
    void addUser(User user);
}

abstract class User {
    protected ChatMediator mediator;
    protected String name;

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

    public abstract void send(String message);
    public abstract void receive(String message);
    public String getName() { return name; }
}

class ChatUser extends User {
    public ChatUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }

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

    @Override
    public void receive(String message) {
        System.out.println(name + " receives: " + message);
    }
}

class ChatRoom implements ChatMediator {
    private final 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) {
            // Don't send the message back to the sender
            if (user != sender) {
                user.receive(message);
            }
        }
    }
}

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

        User alice = new ChatUser(chatRoom, "Alice");
        User bob = new ChatUser(chatRoom, "Bob");
        User charlie = new ChatUser(chatRoom, "Charlie");

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

        alice.send("Hey everyone!");
    }
}

Bad vs Good

Bad Approach

Problems

  • Each user holds direct references to every other user.
  • Adding a new user requires updating every single existing user's contact list.
  • $N$ users equals $N(N-1)$ direct connections (a massive tangled web).
class User {
    private String name;
    private List<User> contacts = new ArrayList<>();

    public void addContact(User user) { contacts.add(user); }

    public void sendMessage(String message) {
        for (User contact : contacts) {
            contact.receive(message);
        }
    }
}

Better Approach

Improvements

  • Zero direct user-to-user references.
  • Adding a new user just means registering with the Mediator — no other users are affected.
  • The tangled web is replaced by a clean star topology.
ChatMediator room = new ChatRoom();
User alice = new ChatUser(room, "Alice");
room.addUser(alice);

alice.send("Hello!"); // Mediator safely routes it

Pros vs Cons

ProsCons
Replaces tightly coupled many-to-many relationships with one-to-manyThe Mediator can easily evolve into an unmaintainable God Object
Colleagues are completely decoupled from each otherIntroduces a single point of failure (if the mediator crashes)
Easy to change complex interaction logic in one central placeCan be massive overkill for simple, direct 1-to-1 interactions
Promotes the Single Responsibility Principle for Colleagues
Reusing individual Colleagues in other contexts becomes easy

When to Use

  • A set of objects communicates in well-defined but complex ways, leading to tangled, unreadable code.
  • You can't reuse a component in a different program because it's heavily coupled to other components.
  • You want to customize distributed behavior between several classes without subclassing them all.

When Not to Use

  • Objects only have simple, straightforward 1:1 relationships.
  • The interaction logic is simple enough that direct communication is perfectly readable.

Real-world Examples

  • java.util.concurrent.ExecutorService (Mediates between tasks and threads).
  • View Controllers in MVC (The Controller often acts as a Mediator between the View and the Model).
  • UI Frameworks (e.g., a Dialog window mediating between its buttons, text fields, and dropdowns).

Key Takeaway

The Mediator Pattern untangles chaotic object communication by forcing all objects to talk through a central hub. Use it when components become so deeply intertwined that changing one breaks five others, but guard aggressively against letting the Mediator grow into a God Object containing all your application's logic.

PreviousMemento PatternNextState Pattern