Mediator Pattern explained simply
Thilan Dissanayaka Software Architecture February 13, 2020

Mediator Pattern explained simply

When multiple objects need to communicate with each other and the direct connections between them start looking like spaghetti, something has gone wrong. If you have five objects and each one talks to the other four, that is twenty direct connections. Add a sixth object and suddenly you are at thirty. This is N-to-N dependency hell, and it gets worse fast.

The Mediator Pattern steps in and says: stop talking to each other directly. Talk through me instead.

What is the Mediator Pattern?

At its core:

  • Define an object that encapsulates how a set of objects interact.
  • Objects no longer refer to each other explicitly – they only know about the mediator.
  • All communication flows through a single central point.

This promotes loose coupling by keeping objects from referring to each other directly. You can change how they interact by simply changing the mediator, without touching any of the individual objects.

A Real-Life Analogy

Think about an airport. Dozens of planes are approaching the runway, taxiing, taking off, and landing – all at the same time. If every pilot had to coordinate directly with every other pilot, it would be chaos. “Hey Flight 302, are you still on runway 7?” “Flight 118, can you hold for a second while I cross?” Absolute nightmare.

Instead, there is an air traffic control tower. Every pilot talks to the tower, and the tower talks to every pilot. No plane communicates with another plane directly. The tower decides who lands, who waits, who takes off, and in what order.

That tower is the mediator. The planes are the colleagues. They don’t need to know about each other – they just need to know about the tower.

Structure

Here is a class diagram showing the Mediator Pattern applied to a chat room system. The ChatMediator interface defines how users communicate, ChatRoom implements the routing logic, and each user only holds a reference to the mediator.

<mxfile>
  <diagram name="Mediator Pattern">
    <mxGraphModel dx="1100" dy="780" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1100" pageHeight="700">
      <root>
        <mxCell id="0"/>
        <mxCell id="1" parent="0"/>

        <!-- ChatMediator interface -->
        <mxCell id="2" value="&lt;&lt;interface&gt;&gt;&#xa;ChatMediator&#xa;─────────────────&#xa;+ sendMessage(msg: String, sender: User): void&#xa;+ addUser(user: User): void" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
          <mxGeometry x="350" y="30" width="380" height="90" as="geometry"/>
        </mxCell>

        <!-- ChatRoom (concrete mediator) -->
        <mxCell id="3" value="ChatRoom&#xa;─────────────────&#xa;- users: List&lt;User&gt;&#xa;+ sendMessage(msg: String, sender: User): void&#xa;+ addUser(user: User): void" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
          <mxGeometry x="350" y="200" width="380" height="100" as="geometry"/>
        </mxCell>

        <!-- User abstract class -->
        <mxCell id="4" value="&lt;&lt;abstract&gt;&gt;&#xa;User&#xa;─────────────────&#xa;# mediator: ChatMediator&#xa;# name: String&#xa;+ send(msg: String): void&#xa;+ receive(msg: String): void" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#fff2cc;strokeColor=#d6b656;" vertex="1" parent="1">
          <mxGeometry x="30" y="180" width="260" height="120" as="geometry"/>
        </mxCell>

        <!-- PremiumUser -->
        <mxCell id="5" value="PremiumUser&#xa;─────────────────&#xa;+ send(msg: String): void&#xa;+ receive(msg: String): void" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
          <mxGeometry x="30" y="400" width="250" height="80" as="geometry"/>
        </mxCell>

        <!-- StandardUser -->
        <mxCell id="6" value="StandardUser&#xa;─────────────────&#xa;+ send(msg: String): void&#xa;+ receive(msg: String): void" style="shape=rectangle;whiteSpace=wrap;html=1;align=center;fontSize=12;fontFamily=monospace;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
          <mxGeometry x="320" y="400" width="250" height="80" as="geometry"/>
        </mxCell>

        <!-- ChatRoom implements ChatMediator -->
        <mxCell id="7" style="endArrow=block;dashed=1;endFill=0;strokeColor=#6c8ebf;" edge="1" source="3" target="2" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>

        <!-- User has-a ChatMediator (association) -->
        <mxCell id="8" value="uses" style="endArrow=open;endFill=0;strokeColor=#d6b656;" edge="1" source="4" target="2" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>

        <!-- ChatRoom has-a Users (composition) -->
        <mxCell id="9" value="manages" style="endArrow=diamond;endFill=1;strokeColor=#82b366;" edge="1" source="3" target="4" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>

        <!-- PremiumUser extends User -->
        <mxCell id="10" style="endArrow=block;endFill=0;strokeColor=#b85450;" edge="1" source="5" target="4" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>

        <!-- StandardUser extends User -->
        <mxCell id="11" style="endArrow=block;endFill=0;strokeColor=#b85450;" edge="1" source="6" target="4" parent="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>
      </root>
    </mxGraphModel>
  </diagram>
</mxfile>

Key Components

  • Mediator (ChatMediator): The interface that defines how colleagues communicate. It declares methods like sendMessage and addUser but doesn’t dictate the implementation.
  • ConcreteMediator (ChatRoom): The actual implementation that coordinates communication between colleague objects. It knows about all participants and routes messages accordingly.
  • Colleague (User): The abstract base class for objects that communicate through the mediator. Each colleague holds a reference to the mediator but knows nothing about other colleagues.
  • ConcreteColleagues (PremiumUser, StandardUser): Specific implementations of the colleague. They send and receive messages through the mediator without any direct references to each other.

Java Example: Chat Room System

Let’s build something concrete. A chat room where users send messages to each other through a mediator. No user knows about any other user – they only know about the chat room.

First, the ChatMediator interface. This is the contract that any mediator must follow:

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

Now the abstract User class. Every user holds a reference to the mediator and has methods to send and receive messages:

public 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;
    }
}

Here is PremiumUser. Premium users receive messages with a special formatting to indicate their status:

public class PremiumUser extends User {

    public PremiumUser(ChatMediator mediator, String name) {
        super(mediator, name);
    }

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

    @Override
    public void receive(String message) {
        System.out.println("[PREMIUM] " + name + " received: *** " + message + " ***");
    }
}

And StandardUser. Standard users get plain messages with no extra formatting:

public class StandardUser extends User {

    public StandardUser(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 + " received: " + message);
    }
}

Now the star of the show – ChatRoom, the concrete mediator. It holds a list of users and routes messages from the sender to everyone else:

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

public class ChatRoom implements ChatMediator {
    private final List<User> users = new ArrayList<>();

    @Override
    public void addUser(User user) {
        users.add(user);
        System.out.println(user.getName() + " joined the chat room.");
    }

    @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(sender.getName() + ": " + message);
            }
        }
    }
}

Notice that ChatRoom is the only class that knows about all the users. Each user only knows about the mediator. That is the entire point.


Putting It Together

public class MainProgram {
    public static void main(String[] args) {
        // Create the mediator
        ChatMediator chatRoom = new ChatRoom();

        // Create users -- they only know about the chat room, not each other
        User alice = new PremiumUser(chatRoom, "Alice");
        User bob = new StandardUser(chatRoom, "Bob");
        User charlie = new StandardUser(chatRoom, "Charlie");
        User diana = new PremiumUser(chatRoom, "Diana");

        // Register users with the mediator
        chatRoom.addUser(alice);
        chatRoom.addUser(bob);
        chatRoom.addUser(charlie);
        chatRoom.addUser(diana);

        System.out.println();

        // Alice sends a message -- routed through the chat room to everyone else
        alice.send("Hey everyone, the deployment is live.");

        System.out.println();

        // Bob replies -- again, through the chat room
        bob.send("Nice, I will run the smoke tests.");
    }
}

Output:

Alice joined the chat room.
Bob joined the chat room.
Charlie joined the chat room.
Diana joined the chat room.

[PREMIUM] Alice sends: Hey everyone, the deployment is live.
Bob received: Alice: Hey everyone, the deployment is live.
Charlie received: Alice: Hey everyone, the deployment is live.
[PREMIUM] Diana received: *** Alice: Hey everyone, the deployment is live. ***

Bob sends: Nice, I will run the smoke tests.
[PREMIUM] Alice received: *** Bob: Nice, I will run the smoke tests. ***
Charlie received: Bob: Nice, I will run the smoke tests.
[PREMIUM] Diana received: *** Bob: Nice, I will run the smoke tests. ***

Alice and Bob and Charlie and Diana never talk to each other directly. Every message goes through ChatRoom. If you want to add filtering, logging, or message history, you change the mediator – not the users.

When to Use the Mediator Pattern

  • When a set of objects communicate in complex but well-defined ways and the resulting interdependencies are hard to understand.
  • When you cannot reuse an object because it refers to and communicates with too many other objects.
  • When you want to customize behavior that is distributed across several classes without creating a ton of subclasses.
  • When the communication logic between objects changes frequently and you want to centralize it in one place.

Advantages

  • Reduces coupling: Objects don’t need to know about each other. They only depend on the mediator interface.
  • Centralizes control: Communication logic lives in one place, making it easier to understand and modify.
  • Simplifies object protocols: Many-to-many relationships are replaced with one-to-many relationships between the mediator and its colleagues.
  • Easier to add new colleagues: Adding a new participant doesn’t require changes to existing ones – just register it with the mediator.

Disadvantages

  • Mediator can become a god object: If you are not careful, the mediator itself can grow into a bloated, complex class that is hard to maintain. You have essentially traded complexity in the network for complexity in one place.
  • Single point of failure: All communication flows through the mediator. If it breaks, everything breaks.
  • Can be overkill: If you only have two or three objects communicating, a mediator adds unnecessary indirection. Use it when the communication graph is genuinely complex.

Real-World Use Cases

This pattern shows up everywhere in production systems:

  • Java Message Service (JMS): The message broker acts as a mediator between producers and consumers. Producers don’t know who consumes their messages and consumers don’t know who produces them.
  • Spring’s ApplicationEventPublisher: Components publish events to the application context, and listeners pick them up. The publisher and listener never reference each other directly – Spring mediates.
  • GUI Dialog Boxes: In a complex form, the submit button, text fields, checkboxes, and dropdowns all need to interact. Instead of wiring them to each other, a dialog controller (mediator) handles all the interaction logic.
  • Microservice Message Brokers (RabbitMQ, Kafka): Services publish messages to a broker and subscribe to topics. The broker mediates all inter-service communication. Service A doesn’t need to know that Service B, C, and D are listening – it just publishes to the broker.
  • Chat applications: Slack, Discord, and similar tools all use a server-side mediator that routes messages between users in channels. No client talks to another client directly.

Wrapping Up

The Mediator Pattern takes a tangled web of object-to-object communication and replaces it with a clean, centralized hub. Instead of N objects each knowing about N-1 others, every object knows about exactly one thing: the mediator.

It trades distributed complexity for centralized control. That is a good trade when the communication graph is complex and changes often. Just keep an eye on the mediator itself – if it starts growing out of control, it might be time to split it into multiple mediators or rethink the design.

The pattern is most valuable when you have many collaborating objects with complex interaction rules. Start with direct communication if the relationships are simple. Reach for the mediator when the spaghetti starts forming.

ALSO READ
Writing a Shell Code for Linux
Apr 21, 2020 Exploit Development

Shellcode is a small piece of machine code used as the payload in exploit development. In this post, we write Linux shellcode from scratch — starting with a simple exit, building up to spawning a shell, and explaining every decision along the way.

Exploiting a Stack Buffer Overflow on Windows
Apr 12, 2020 Exploit Development

In a previous tutorial we discusses how we can exploit a buffer overflow vulnerability on a Linux machine. I wen through all theories in depth and explained each step. Now today we are going to jump...

Exploiting a  Stack Buffer Overflow  on Linux
Apr 01, 2020 Exploit Development

Have you ever wondered how attackers gain control over remote servers? How do they just run some exploit and compromise a computer? If we dive into the actual context, there is no magic happening....

Basic concepts of Cryptography
Mar 01, 2020 Cryptography

Ever notice that little padlock icon in your browser's address bar? That's cryptography working silently in the background, protecting everything you do online. Whether you're sending an email,...

Common Web Application Attacks
Feb 05, 2020 Application Security

Web applications are one of the most targeted surfaces by attackers. This is primarily because they are accessible over the internet, making them exposed and potentially vulnerable. Since these...

Remote Code Execution (RCE)
Jan 02, 2020 Application Security

Remote Code Execution (RCE) is the holy grail of application security vulnerabilities. It allows an attacker to execute arbitrary code on a remote server — and the consequences are as bad as it sounds. In this post, we'll go deep into RCE across multiple languages, including PHP, Java, Python, and Node.js.

> > >