Skip to main content

Mediator Pattern

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

Problem

You have a set of objects that communicate directly with each other, creating a tightly coupled web of dependencies. Any change to one object requires understanding and updating all the others it talks to.

Common Scenarios:

  • Chat rooms where users send messages through a central hub
  • Air traffic control systems coordinating aircraft
  • UI forms where enabling one component affects others
  • Event bus or message broker systems
  • Smart home hubs coordinating devices

Design Principles Applied

  1. Principle of Least Knowledge - Colleagues only know the mediator, not each other
  2. Single Responsibility - Interaction logic is centralised in the mediator
  3. Open/Closed Principle - Add new colleagues without changing existing ones

UML Diagram

Implementation

Example 1: Chat Room

Users send messages through a central chat room rather than directly to each other.

Step 1: Define the Mediator Interface

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

Step 2: Define the Colleague

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

Step 3: Create the Concrete Mediator

public 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("[" + sender.name + "]: " + message);
}
}
}
}

Step 4: Create Concrete Colleagues

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

Step 5: Client Usage

ChatRoom chatRoom = new ChatRoom();

ChatUser alice = new ChatUser(chatRoom, "Alice");
ChatUser bob = new ChatUser(chatRoom, "Bob");
ChatUser carol = new ChatUser(chatRoom, "Carol");

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

alice.send("Hello everyone!");
// Alice sends: Hello everyone!
// Bob receives: [Alice]: Hello everyone!
// Carol receives: [Alice]: Hello everyone!

bob.send("Hey Alice!");
// Bob sends: Hey Alice!
// Alice receives: [Bob]: Hey Alice!
// Carol receives: [Bob]: Hey Alice!

Example 2: UI Form Mediator

A dialog box where components affect each other — checking a checkbox enables a text field and a button.

public interface DialogMediator {
void componentChanged(Component component);
}

public abstract class Component {
protected DialogMediator mediator;

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

public class Checkbox extends Component {
private boolean checked;

public Checkbox(DialogMediator mediator) { super(mediator); }

public boolean isChecked() { return checked; }

public void setChecked(boolean checked) {
this.checked = checked;
mediator.componentChanged(this);
}
}

public class TextField extends Component {
private boolean enabled;

public TextField(DialogMediator mediator) { super(mediator); }

public void setEnabled(boolean enabled) {
this.enabled = enabled;
System.out.println("TextField is now " + (enabled ? "enabled" : "disabled"));
}
}

public class SubmitButton extends Component {
private boolean enabled;

public SubmitButton(DialogMediator mediator) { super(mediator); }

public void setEnabled(boolean enabled) {
this.enabled = enabled;
System.out.println("Submit button is now " + (enabled ? "enabled" : "disabled"));
}
}

public class RegistrationDialog implements DialogMediator {
private final Checkbox agreeCheckbox;
private final TextField nameField;
private final SubmitButton submitButton;

public RegistrationDialog() {
agreeCheckbox = new Checkbox(this);
nameField = new TextField(this);
submitButton = new SubmitButton(this);
}

@Override
public void componentChanged(Component component) {
if (component == agreeCheckbox) {
boolean agreed = agreeCheckbox.isChecked();
nameField.setEnabled(agreed);
submitButton.setEnabled(agreed);
}
}
}

// Usage
RegistrationDialog dialog = new RegistrationDialog();
dialog.agreeCheckbox.setChecked(true);
// TextField is now enabled
// Submit button is now enabled

Real-World Examples

  • Java Message Service (JMS) — message broker acts as mediator between producers and consumers
  • Spring ApplicationEventPublisher — components publish events; the publisher routes them to listeners
  • Redux store (JavaScript) — central store mediates state changes between UI components
  • Air Traffic Control — all aircraft communicate through the tower, not with each other directly

Mediator vs Similar Patterns

PatternIntentCoupling
MediatorCentralise complex interactions between many objectsColleagues are decoupled from each other, coupled to mediator
ObserverNotify dependents of state changesSubject decoupled from observers via interface
FacadeSimplify a complex subsystemOne-way — client talks to facade, not vice versa
CommandEncapsulate a request as an objectDecouples invoker from receiver, no coordination hub