The Mediator pattern is a behavioral design pattern in software engineering that defines an object, known as the mediator, which encapsulates the interactions among a set of objects, thereby promoting loose coupling by preventing those objects from directly referencing each other.[1] This pattern was first formally described in the seminal 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, often referred to as the "Gang of Four" (GoF).[1] By centralizing communication through the mediator, the pattern addresses the problem of tightly coupled components—such as user interface elements in a dialog box—that become difficult to maintain, reuse, or extend due to their interdependent relationships.[2]
In the Mediator pattern, participating objects, called colleagues or components, communicate solely through the mediator rather than directly with one another, reducing the complexity of many-to-many interactions to simpler one-to-many relationships.[3] The structure typically involves a Mediator interface that declares methods for notifying the mediator of events, concrete colleague classes that hold a reference to the mediator and invoke its methods upon state changes, and a concrete mediator that coordinates the colleagues by maintaining references to them and directing their interactions.[2] This design allows colleagues to remain independent and reusable, as changes to their interactions can be managed centrally in the mediator without altering the colleagues themselves.[3]
The pattern is particularly applicable in scenarios involving complex subsystems, such as graphical user interfaces, air traffic control systems, or chat rooms, where direct coupling would lead to inflexible code, or when numerous subclasses would otherwise be needed to capture varying behaviors in different contexts.[2] It works by having colleagues send notifications to the mediator upon relevant events, after which the mediator evaluates the context and orchestrates appropriate responses among the colleagues, often leveraging polymorphism for flexible handling.[3] Key benefits include simplified maintenance through reduced dependencies, adherence to the open-closed principle (allowing extension without modification), and enhanced reusability of individual components.[2] However, a potential drawback is the risk of the mediator evolving into a monolithic "god object" that centralizes too much responsibility, complicating its own maintenance over time.[3]
Compared to related patterns, the Mediator differs from the Observer pattern by centralizing control in a single mediator rather than distributing notifications among subjects and observers, and from the Facade pattern by providing a more dynamic and interactive coordination mechanism rather than a static simplified interface to a subsystem.[2] It can also integrate with the Observer pattern to enable dynamic registration of colleagues with the mediator.[3] Overall, the Mediator pattern remains a foundational tool for designing decoupled, scalable object-oriented systems.[1]
Introduction
Intent and Motivation
The Mediator pattern is a behavioral design pattern that promotes loose coupling among a set of objects by centralizing their communication through a dedicated mediator object, thereby encapsulating the complex interaction logic that would otherwise result in direct, tangled dependencies between the objects themselves.[2][4] This approach restricts direct communications, forcing objects—known as colleagues—to interact exclusively via the mediator, which coordinates their behavior and resolves any interdependencies without requiring the colleagues to maintain references to one another.[5] By doing so, the pattern simplifies the overall system architecture, making it easier to modify individual components independently while preserving the integrity of their interactions.[6]
The motivation for employing the Mediator pattern arises in scenarios where multiple objects must collaborate in a coordinated manner but direct pairwise connections would lead to excessive complexity and maintenance challenges. For instance, in an air traffic control system, aircraft (as colleague objects) need to exchange position updates and clearance instructions without each plane directly communicating with every other, as this would create a web of dependencies prone to errors and scalability issues; instead, a central control tower acts as the mediator to manage all transmissions and ensure safe coordination.[2] Similarly, in a chat room application, users send messages through a mediator server that broadcasts them to relevant participants, avoiding the need for each user to know the details of others' connections and thereby reducing network complexity and enhancing modularity.[5] These examples illustrate how the pattern is particularly valuable in distributed or event-driven systems where indirect, mediated interactions prevent the proliferation of dependencies that could otherwise hinder reusability and extensibility.[5]
The Mediator pattern was formalized in the seminal 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides—commonly known as the Gang of Four (GoF)—which emphasized its role in centralizing intricate communication networks to foster more maintainable object-oriented designs.[4] This historical introduction positioned the pattern as a key solution for addressing the challenges of object collaboration in early software engineering practices, influencing its widespread adoption in object-oriented designs.
Problems Addressed
In object-oriented systems without a centralized coordination mechanism, classes often develop direct dependencies on one another, resulting in tight coupling that complicates reuse and maintenance.[2] For instance, when multiple classes must reference and interact with each other explicitly, modifications to one class can propagate unintended changes across the entire system, fostering a "spaghetti code" structure where interdependencies become chaotic and difficult to trace.[3] This issue is exacerbated in evolving applications, as the growing web of relations between components hinders independent development and increases the risk of introducing bugs during updates.[2]
Scalability challenges arise particularly in systems involving numerous interacting components, such as graphical user interfaces (GUIs) or networked devices, where direct communications lead to high coupling and violate the single responsibility principle.[5] In GUIs, for example, elements like buttons, text fields, and checkboxes may need to respond to each other's states, creating a tangled network of references that makes the system brittle and hard to extend as new components are added.[2] Similarly, in distributed environments like networked devices, each component's direct awareness of others inflates complexity, raising maintenance costs and limiting the ability to scale without widespread refactoring.[3] This coupling forces classes to handle not only their core logic but also intricate communication protocols, breaching the principle that a class should have only one reason to change.[5]
A concrete illustration of these pre-mediator issues occurs in a chat application, where each user class must maintain references to all other users to broadcast messages, leading to an n-to-n dependency graph that becomes unmanageable with more participants.[5] Adding or removing users requires updating every affected class, amplifying the ripple effects of changes and turning simple enhancements into labor-intensive tasks.[3] In such scenarios, the absence of a coordination hub results in redundant code for message routing and state synchronization, further entangling the system's architecture.[2]
Core Concepts
Definition
The Mediator pattern defines an object that encapsulates how a set of objects interact, thereby centralizing communication and coordination among them.[7] This approach promotes loose coupling by preventing objects from referencing each other directly, allowing their interactions to vary independently without affecting the underlying structure.[7]
Classified as a behavioral design pattern, the Mediator is one of the 23 patterns documented in the influential 1994 book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, known as the Gang of Four (GoF). Behavioral patterns like Mediator focus on algorithms and the assignment of responsibilities between objects to ensure flexible and efficient collaboration.
At its core, the pattern relies on a mediator interface that declares abstract methods for notifying and coordinating interactions among colleague objects, while concrete mediator implementations handle the specific logic for routing messages and managing dependencies.[7] This encapsulation reduces the complexity of direct interconnections, such as those arising from tight coupling in multi-object systems.[7]
Key Components
The Mediator pattern revolves around a core set of components that enable objects to interact indirectly, thereby promoting loose coupling and centralized control over communications. These components include the Mediator interface, the Concrete Mediator, and the Colleague classes, each with distinct roles in managing interactions.[8]
The Mediator interface serves as an abstract contract defining the methods through which colleagues can notify the mediator of events or changes in state. It typically declares a single primary method, such as notify(sender, event), which allows colleagues to communicate without knowing the details of other components. This interface ensures that all interactions are routed through a common protocol, encapsulating the complexity of object relationships.[2][8]
The Concrete Mediator is the implementation of the Mediator interface that orchestrates the actual coordination among colleagues. It maintains references to all participating colleague objects and responds to notifications by evaluating the event and invoking appropriate methods on the relevant colleagues. By centralizing the logic for interaction rules—such as determining which objects should react to a given event—the Concrete Mediator prevents direct dependencies between colleagues, making the system more maintainable and scalable.[2][8]
Colleague classes represent the participating objects in the system, each responsible for its own specific functionality while relying on the mediator for any inter-object communication. Rather than holding references to other colleagues, each Colleague instance references the Mediator interface and uses it to broadcast events or requests, such as state changes, ensuring that colleagues remain unaware of each other's existence or implementation details. Concrete implementations of Colleagues, such as buttons or displays in a user interface, focus exclusively on their internal logic and delegate all collaborative behaviors to the mediator.[2][8]
In terms of responsibilities, Colleagues concentrate on their domain-specific tasks and avoid embedding interaction logic, which is instead handled by the mediator to enforce routing rules and manage event propagation. This division allows for easier modification of communication behaviors without altering individual colleague classes, as the mediator absorbs the complexity of coordinating responses across the system.[2][8]
Design Structure
Class Diagram
The UML class diagram for the Mediator pattern depicts the static relationships among classes that enable centralized communication between objects, reducing direct dependencies. As outlined in the foundational text by Gamma, Helm, Johnson, and Vlissides (1994), the diagram centers on an abstract Mediator class or interface that declares methods for coordinating interactions, such as notify(sender: Colleague, event: [string](/page/String)), which processes requests from associated objects.
A ConcreteMediator subclass implements the Mediator, maintaining references to multiple ConcreteColleague instances through composition, allowing it to orchestrate their behaviors—for instance, by updating one colleague based on changes in another.[9] The Colleague superclass or interface includes a reference to the Mediator (e.g., a mediator: Mediator attribute) and abstract methods like send() or notify(), which delegates communication to the mediator rather than directly to peers. ConcreteColleague classes extend Colleague, encapsulating specific functionality while relying on the mediator for inter-object coordination.[3]
Relationships in the diagram show inheritance arrows from ConcreteMediator to Mediator and from ConcreteColleagues to Colleague, indicating generalization. Dependency arrows point from Colleague to Mediator, representing the unidirectional reference each colleague holds. Bidirectional associations link the ConcreteMediator to ConcreteColleagues, emphasizing the mediator's role in managing the group. Multiplicities are typically notated as 1 (one mediator instance) to * (multiple colleagues), with the association often labeled to clarify directionality.[5]
Notations distinguish between abstract elements—using italics or <> for Mediator and Colleague—and concrete implementations, with <> stereotypes applied when using interfaces for platform independence, as opposed to abstract classes for shared state. This structure ensures colleagues remain unaware of each other, fostering modularity.[9]
Sequence Diagram
The UML sequence diagram for the Mediator pattern illustrates the runtime interactions among colleagues through the central mediator, emphasizing how direct communications are replaced by indirect notifications and coordinated responses. In a typical scenario, a concrete colleague (e.g., a user interface component) detects an event, such as a state change, and sends a notification message to the mediator via a method like notify(sender, event). This initiates an activation bar on the mediator's lifeline, representing its processing phase, during which it evaluates the event—potentially using internal conditional logic to determine recipients—before forwarding targeted requests to other relevant colleagues, such as update(newState). Return messages may flow back to the originator if acknowledgments or results are required, ensuring all interactions remain encapsulated within the mediator.[10]
Key interactions highlight the pattern's decoupling: the notification from the colleague to the mediator serves as the sole entry point for events, avoiding peer-to-peer calls; the mediator then performs conditional routing based on the current system state or event type (e.g., querying the sender for details via getChange() before propagating via setChange()), which prevents colleagues from needing knowledge of each other. For instance, in a dialog box example, selecting a file filter in one widget triggers a mediator notification, leading to updates across list views and other components without direct coupling. Responses, if synchronous, return along the same path, maintaining a clear flow of control.[10][2]
Variations in the sequence diagram reflect mediation complexity: in simple cases, the mediator acts as a straightforward router, directly relaying messages to predefined colleagues with minimal internal processing; in more complex scenarios, alt fragments denote event-specific branches (e.g., [if event == "login"] enable form else disable), and self-messages on the mediator's lifeline depict internal state updates or validations before forwarding. These elements underscore the pattern's flexibility for handling dynamic dependencies, as originally outlined in the foundational work on behavioral design patterns.[2][10]
Implementation
Pseudocode
The Mediator pattern's core logic is captured in abstract pseudocode, which defines the interface for communication and demonstrates how colleagues interact indirectly through the mediator to encapsulate dependencies. This abstraction highlights the pattern's role in centralizing control without tying it to any specific programming language.
The Mediator interface declares a single method for colleagues to notify the mediator of events, passing the sender and event details for routing.
interface Mediator {
void notify(Colleague sender, String event);
}
interface Mediator {
void notify(Colleague sender, String event);
}
A concrete mediator, such as a chat room coordinator, maintains a list of registered colleagues in its constructor and implements the notify method to inspect the event and broadcast it appropriately—excluding the sender—to reduce direct couplings.
class ChatRoom implements Mediator {
List<Colleague> users = [];
// Constructor initializes the list of colleagues
ChatRoom() {
// List is empty initially; users added via addUser
}
void addUser(Colleague user) {
users.add(user);
user.setMediator(this);
}
void notify(Colleague sender, String event) {
if (event.startsWith("message:")) {
// Broadcast message to all other users
for (Colleague user : users) {
if (user != sender) {
user.receive(event);
}
}
}
// Additional event handling can be added here, e.g., for "join" or "leave"
}
}
class ChatRoom implements Mediator {
List<Colleague> users = [];
// Constructor initializes the list of colleagues
ChatRoom() {
// List is empty initially; users added via addUser
}
void addUser(Colleague user) {
users.add(user);
user.setMediator(this);
}
void notify(Colleague sender, String event) {
if (event.startsWith("message:")) {
// Broadcast message to all other users
for (Colleague user : users) {
if (user != sender) {
user.receive(event);
}
}
}
// Additional event handling can be added here, e.g., for "join" or "leave"
}
}
The Colleague base class holds a reference to the mediator and provides a send method that delegates events to the mediator; concrete colleagues implement receive to update their internal state based on incoming notifications.
abstract class Colleague {
Mediator mediator;
void setMediator(Mediator m) {
mediator = m;
}
void send([String](/page/String) event) {
[mediator](/page/The_Mediator).notify(this, event);
}
abstract void receive([String](/page/String) event);
}
abstract class Colleague {
Mediator mediator;
void setMediator(Mediator m) {
mediator = m;
}
void send([String](/page/String) event) {
[mediator](/page/The_Mediator).notify(this, event);
}
abstract void receive([String](/page/String) event);
}
In a full chat room example, users are instantiated as concrete colleagues, registered with the mediator, and use the send method to propagate messages, which the mediator then routes to recipients for processing—ensuring no direct user-to-user communication. This flow illustrates loose coupling, as users remain unaware of each other.
class User extends Colleague {
String name;
User(String name) {
this.name = name;
}
void receive(String event) {
// Update internal state, e.g., display the message
print(name + " received: " + event);
}
// Example usage flow
// ChatRoom room = new ChatRoom();
// User alice = new User("Alice");
// User bob = new User("Bob");
// room.addUser(alice);
// room.addUser(bob);
// alice.send("message:Hello from Alice!");
// // Triggers: bob.receive("message:Hello from Alice!")
}
class User extends Colleague {
String name;
User(String name) {
this.name = name;
}
void receive(String event) {
// Update internal state, e.g., display the message
print(name + " received: " + event);
}
// Example usage flow
// ChatRoom room = new ChatRoom();
// User alice = new User("Alice");
// User bob = new User("Bob");
// room.addUser(alice);
// room.addUser(bob);
// alice.send("message:Hello from Alice!");
// // Triggers: bob.receive("message:Hello from Alice!")
}
Language Examples
The Mediator pattern is commonly illustrated through implementations in object-oriented languages like C# and Java, where it centralizes communication to reduce direct dependencies between objects. These examples demonstrate how colleagues interact solely through the mediator, promoting loose coupling as described in the original design patterns literature.[11]
C# Example
In C#, the Mediator pattern can be implemented using an interface for the mediator and classes for colleagues, often in a chat room scenario where users send messages via the mediator without direct references to each other. The following code shows an IMediator interface with a Notify method, a ChatMediator concrete class maintaining a list of User colleagues, and a User class implementing colleague behavior with a SendMessage method that invokes the mediator. Exception handling is incorporated in the mediator to manage cases like unregistered recipients.[12]
csharp
using System;
using System.Collections.Generic;
public interface IMediator
{
void Notify(string message, [User](/page/User) sender);
}
public class ChatMediator : IMediator
{
private List<[User](/page/User)> colleagues = new List<[User](/page/User)>();
public void AddColleague([User](/page/User) colleague)
{
colleague.Mediator = this;
colleagues.Add(colleague);
}
public void Notify(string message, [User](/page/User) sender)
{
foreach (var colleague in colleagues)
{
if (colleague != sender)
{
try
{
colleague.Receive(message);
}
catch (Exception ex)
{
Console.WriteLine($"Error delivering message: {ex.Message}");
}
}
}
}
}
public abstract class User
{
protected IMediator Mediator;
public string Name { get; set; }
protected User(IMediator mediator, string name)
{
Mediator = mediator;
Name = name;
}
public void SendMessage(string message)
{
Console.WriteLine($"{Name} sends: {message}");
Mediator.Notify(message, this);
}
public abstract void Receive(string message);
}
public class ConcreteUser : User
{
public ConcreteUser(IMediator mediator, string name) : base(mediator, name) { }
public override void Receive(string message)
{
Console.WriteLine($"{Name} receives: {message}");
}
}
// Instantiation and usage
class Program
{
static void Main()
{
ChatMediator mediator = new ChatMediator();
User user1 = new ConcreteUser(mediator, "Alice");
User user2 = new ConcreteUser(mediator, "Bob");
User user3 = new ConcreteUser(mediator, "Charlie");
mediator.AddColleague(user1);
mediator.AddColleague(user2);
mediator.AddColleague(user3);
user1.SendMessage("Hello everyone!");
user2.SendMessage("Hi Alice!");
}
}
using System;
using System.Collections.Generic;
public interface IMediator
{
void Notify(string message, [User](/page/User) sender);
}
public class ChatMediator : IMediator
{
private List<[User](/page/User)> colleagues = new List<[User](/page/User)>();
public void AddColleague([User](/page/User) colleague)
{
colleague.Mediator = this;
colleagues.Add(colleague);
}
public void Notify(string message, [User](/page/User) sender)
{
foreach (var colleague in colleagues)
{
if (colleague != sender)
{
try
{
colleague.Receive(message);
}
catch (Exception ex)
{
Console.WriteLine($"Error delivering message: {ex.Message}");
}
}
}
}
}
public abstract class User
{
protected IMediator Mediator;
public string Name { get; set; }
protected User(IMediator mediator, string name)
{
Mediator = mediator;
Name = name;
}
public void SendMessage(string message)
{
Console.WriteLine($"{Name} sends: {message}");
Mediator.Notify(message, this);
}
public abstract void Receive(string message);
}
public class ConcreteUser : User
{
public ConcreteUser(IMediator mediator, string name) : base(mediator, name) { }
public override void Receive(string message)
{
Console.WriteLine($"{Name} receives: {message}");
}
}
// Instantiation and usage
class Program
{
static void Main()
{
ChatMediator mediator = new ChatMediator();
User user1 = new ConcreteUser(mediator, "Alice");
User user2 = new ConcreteUser(mediator, "Bob");
User user3 = new ConcreteUser(mediator, "Charlie");
mediator.AddColleague(user1);
mediator.AddColleague(user2);
mediator.AddColleague(user3);
user1.SendMessage("Hello everyone!");
user2.SendMessage("Hi Alice!");
}
}
This example aligns with the abstract pseudocode structure by encapsulating notification logic in the mediator. The output demonstrates broadcast: Alice's message reaches Bob and Charlie, and Bob's reaches Alice and Charlie. Event types could be extended using an enum like enum EventType { Message, Join, Leave } passed to Notify for more sophisticated mediation.
Java Example
In Java, the Mediator pattern is frequently demonstrated with a graphical user interface simulation, such as a dialog box for note management, where UI components like buttons, lists, and text fields communicate exclusively through a mediator to coordinate actions like adding, deleting, or filtering notes. The structure uses a Mediator interface, an abstract Colleague (here, Component), and concrete classes, with the Editor as the concrete mediator handling interactions. Full runnable code includes exception handling in methods like saving changes and a main method for system instantiation. This simulates a dialog box by managing visibility and updates among components.[13]
java
// Note.java (supporting class)
package mediator.example;
public class Note {
private String name;
private String text;
public Note() { name = "New Note"; text = ""; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
// Component.java (abstract colleague)
package mediator.example;
public interface Component {
void setMediator([Mediator](/page/The_Mediator) mediator);
[String](/page/String) getName();
}
// Mediator.java
package mediator.example;
import javax.swing.ListModel;
public interface Mediator {
void addNewNote(Note note);
void deleteNote();
void getInfoFromList(Note note);
void saveChanges();
void markNote();
void clear();
void sendToFilter(ListModel listModel);
void setElementsList(ListModel list);
void registerComponent(Component component);
void hideElements(boolean flag);
void createGUI();
}
// AddButton.java (concrete colleague)
package mediator.example;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class AddButton extends JButton implements Component {
private Mediator mediator;
public AddButton() { super("Add"); }
@Override public void setMediator([Mediator](/page/The_Mediator) mediator) { this.mediator = mediator; }
@Override protected void fireActionPerformed(ActionEvent e) { mediator.addNewNote(new [Note](/page/Note)()); }
@Override public String getName() { return "AddButton"; }
}
// DeleteButton.java
package mediator.example;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class DeleteButton extends JButton implements Component {
private [Mediator](/page/The_Mediator) mediator;
public DeleteButton() { super("[Del](/page/Del)"); }
@Override public void setMediator([Mediator](/page/The_Mediator) mediator) { this.mediator = mediator; }
@Override protected void fireActionPerformed(ActionEvent e) { mediator.deleteNote(); }
@Override public String getName() { return "DelButton"; }
}
// Filter.java
package mediator.example;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
public class Filter extends JTextField implements Component {
private [Mediator](/page/The_Mediator) mediator;
private ListModel listModel;
public Filter() { super("Search notes..."); }
@Override public void setMediator([Mediator](/page/The_Mediator) mediator) { this.mediator = mediator; }
@Override protected void processComponentKeyEvent(KeyEvent e) {
String start = getText(); mediator.sendToFilter(listModel); // Simplified search trigger
}
public void setList(ListModel listModel) { this.listModel = listModel; }
@Override public String getName() { return "[Filter](/page/Filter)"; }
}
// ListModel and List implementation (simplified for brevity; uses DefaultListModel<Note>)
import javax.swing.*;
import java.util.EventObject;
public class List extends JList<Note> implements Component {
private Mediator mediator;
private DefaultListModel<Note> listModel = new DefaultListModel<>();
public List() { super(); setModel(listModel); }
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
public void addElement(Note note) { listModel.addElement(note); }
public void deleteElement() {
int index = getSelectedIndex();
if (index >= 0) listModel.remove(index);
}
public Note getCurrentElement() { return (Note) getSelectedValue(); }
@Override public String getName() { return "List"; }
}
// SaveButton.java
package mediator.example;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class SaveButton extends JButton implements Component {
private Mediator mediator;
public SaveButton() { super("Save"); }
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override protected void fireActionPerformed(ActionEvent e) { mediator.saveChanges(); }
@Override public String getName() { return "SaveButton"; }
}
// TextBox.java and Title.java (similar, with key event handling to mark note)
package mediator.example;
import javax.swing.*;
import java.awt.event.KeyEvent;
public class Title extends JTextField implements Component {
private Mediator mediator;
public Title() { super(); }
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override protected void processComponentKeyEvent(KeyEvent e) { mediator.markNote(); }
@Override public String getName() { return "Title"; }
}
public class TextBox extends JTextArea implements Component {
private Mediator mediator;
public TextBox() { super(); }
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override protected void processComponentKeyEvent(KeyEvent e) { mediator.markNote(); }
@Override public String getName() { return "TextBox"; }
}
// Editor.java (concrete mediator)
package mediator.example;
import javax.swing.*;
import java.awt.*;
import java.util.EventObject;
public class Editor implements Mediator {
private Title title = new Title();
private TextBox textBox = new TextBox();
private AddButton add = new AddButton();
private DeleteButton del = new DeleteButton();
private SaveButton save = new SaveButton();
private List list = new List();
private Filter filter = new Filter();
private JLabel label = new JLabel("Add or select existing note to proceed...");
@Override public void registerComponent(Component component) {
component.setMediator(this);
switch (component.getName()) {
case "AddButton": add = (AddButton) component; break;
case "DelButton": del = (DeleteButton) component; break;
case "Filter": filter = (Filter) component; break;
case "List": list = (List) component; break;
case "SaveButton": save = (SaveButton) component; break;
case "TextBox": textBox = (TextBox) component; break;
case "Title": title = (Title) component; break;
}
}
@Override public void addNewNote(Note note) {
title.setText(""); textBox.setText(""); list.addElement(note);
}
@Override public void deleteNote() { list.deleteElement(); }
@Override public void getInfoFromList(Note note) {
title.setText(note.getName().replace('*', ' ')); textBox.setText(note.getText());
}
@Override public void saveChanges() {
try {
Note note = list.getCurrentElement();
if (note != null) {
note.setName(title.getText()); note.setText(textBox.getText());
list.repaint();
}
} catch (NullPointerException ignored) {}
}
@Override public void markNote() {
try {
Note note = list.getCurrentElement();
if (note != null && !note.getName().endsWith("*")) {
note.setName(note.getName() + "*");
list.repaint();
}
} catch (NullPointerException ignored) {}
}
@Override public void clear() { title.setText(""); textBox.setText(""); }
@Override public void sendToFilter(ListModel listModel) { filter.setList(listModel); }
@Override public void setElementsList(ListModel list) { list.setModel(list); list.repaint(); }
@Override public void hideElements(boolean flag) {
title.setVisible(!flag); textBox.setVisible(!flag); save.setVisible(!flag); label.setVisible(flag);
}
@Override public void createGUI() {
// Simplified GUI setup (in full code, use JPanel, add components, set layout)
JFrame frame = new JFrame("Mediator Example - Note Dialog");
frame.setLayout(new BorderLayout());
frame.add(add, BorderLayout.NORTH);
frame.add(del, BorderLayout.NORTH);
frame.add(filter, BorderLayout.NORTH);
frame.add(list, BorderLayout.CENTER);
frame.add(title, BorderLayout.SOUTH);
frame.add(textBox, BorderLayout.SOUTH);
frame.add(save, BorderLayout.SOUTH);
frame.add(label, BorderLayout.SOUTH);
frame.setSize(400, 300);
frame.setVisible(true);
registerComponent(add); registerComponent(del); registerComponent(filter);
registerComponent(list); registerComponent(save); registerComponent(title);
registerComponent(textBox);
}
}
// Instantiation (main method)
package mediator.example;
public class Main {
public static void main(String[] args) {
Editor editor = new Editor();
editor.createGUI();
// Interact via GUI; e.g., clicking Add creates new note, handled by mediator
}
}
// Note.java (supporting class)
package mediator.example;
public class Note {
private String name;
private String text;
public Note() { name = "New Note"; text = ""; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
}
// Component.java (abstract colleague)
package mediator.example;
public interface Component {
void setMediator([Mediator](/page/The_Mediator) mediator);
[String](/page/String) getName();
}
// Mediator.java
package mediator.example;
import javax.swing.ListModel;
public interface Mediator {
void addNewNote(Note note);
void deleteNote();
void getInfoFromList(Note note);
void saveChanges();
void markNote();
void clear();
void sendToFilter(ListModel listModel);
void setElementsList(ListModel list);
void registerComponent(Component component);
void hideElements(boolean flag);
void createGUI();
}
// AddButton.java (concrete colleague)
package mediator.example;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class AddButton extends JButton implements Component {
private Mediator mediator;
public AddButton() { super("Add"); }
@Override public void setMediator([Mediator](/page/The_Mediator) mediator) { this.mediator = mediator; }
@Override protected void fireActionPerformed(ActionEvent e) { mediator.addNewNote(new [Note](/page/Note)()); }
@Override public String getName() { return "AddButton"; }
}
// DeleteButton.java
package mediator.example;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class DeleteButton extends JButton implements Component {
private [Mediator](/page/The_Mediator) mediator;
public DeleteButton() { super("[Del](/page/Del)"); }
@Override public void setMediator([Mediator](/page/The_Mediator) mediator) { this.mediator = mediator; }
@Override protected void fireActionPerformed(ActionEvent e) { mediator.deleteNote(); }
@Override public String getName() { return "DelButton"; }
}
// Filter.java
package mediator.example;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
public class Filter extends JTextField implements Component {
private [Mediator](/page/The_Mediator) mediator;
private ListModel listModel;
public Filter() { super("Search notes..."); }
@Override public void setMediator([Mediator](/page/The_Mediator) mediator) { this.mediator = mediator; }
@Override protected void processComponentKeyEvent(KeyEvent e) {
String start = getText(); mediator.sendToFilter(listModel); // Simplified search trigger
}
public void setList(ListModel listModel) { this.listModel = listModel; }
@Override public String getName() { return "[Filter](/page/Filter)"; }
}
// ListModel and List implementation (simplified for brevity; uses DefaultListModel<Note>)
import javax.swing.*;
import java.util.EventObject;
public class List extends JList<Note> implements Component {
private Mediator mediator;
private DefaultListModel<Note> listModel = new DefaultListModel<>();
public List() { super(); setModel(listModel); }
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
public void addElement(Note note) { listModel.addElement(note); }
public void deleteElement() {
int index = getSelectedIndex();
if (index >= 0) listModel.remove(index);
}
public Note getCurrentElement() { return (Note) getSelectedValue(); }
@Override public String getName() { return "List"; }
}
// SaveButton.java
package mediator.example;
import javax.swing.*;
import java.awt.event.ActionEvent;
public class SaveButton extends JButton implements Component {
private Mediator mediator;
public SaveButton() { super("Save"); }
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override protected void fireActionPerformed(ActionEvent e) { mediator.saveChanges(); }
@Override public String getName() { return "SaveButton"; }
}
// TextBox.java and Title.java (similar, with key event handling to mark note)
package mediator.example;
import javax.swing.*;
import java.awt.event.KeyEvent;
public class Title extends JTextField implements Component {
private Mediator mediator;
public Title() { super(); }
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override protected void processComponentKeyEvent(KeyEvent e) { mediator.markNote(); }
@Override public String getName() { return "Title"; }
}
public class TextBox extends JTextArea implements Component {
private Mediator mediator;
public TextBox() { super(); }
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override protected void processComponentKeyEvent(KeyEvent e) { mediator.markNote(); }
@Override public String getName() { return "TextBox"; }
}
// Editor.java (concrete mediator)
package mediator.example;
import javax.swing.*;
import java.awt.*;
import java.util.EventObject;
public class Editor implements Mediator {
private Title title = new Title();
private TextBox textBox = new TextBox();
private AddButton add = new AddButton();
private DeleteButton del = new DeleteButton();
private SaveButton save = new SaveButton();
private List list = new List();
private Filter filter = new Filter();
private JLabel label = new JLabel("Add or select existing note to proceed...");
@Override public void registerComponent(Component component) {
component.setMediator(this);
switch (component.getName()) {
case "AddButton": add = (AddButton) component; break;
case "DelButton": del = (DeleteButton) component; break;
case "Filter": filter = (Filter) component; break;
case "List": list = (List) component; break;
case "SaveButton": save = (SaveButton) component; break;
case "TextBox": textBox = (TextBox) component; break;
case "Title": title = (Title) component; break;
}
}
@Override public void addNewNote(Note note) {
title.setText(""); textBox.setText(""); list.addElement(note);
}
@Override public void deleteNote() { list.deleteElement(); }
@Override public void getInfoFromList(Note note) {
title.setText(note.getName().replace('*', ' ')); textBox.setText(note.getText());
}
@Override public void saveChanges() {
try {
Note note = list.getCurrentElement();
if (note != null) {
note.setName(title.getText()); note.setText(textBox.getText());
list.repaint();
}
} catch (NullPointerException ignored) {}
}
@Override public void markNote() {
try {
Note note = list.getCurrentElement();
if (note != null && !note.getName().endsWith("*")) {
note.setName(note.getName() + "*");
list.repaint();
}
} catch (NullPointerException ignored) {}
}
@Override public void clear() { title.setText(""); textBox.setText(""); }
@Override public void sendToFilter(ListModel listModel) { filter.setList(listModel); }
@Override public void setElementsList(ListModel list) { list.setModel(list); list.repaint(); }
@Override public void hideElements(boolean flag) {
title.setVisible(!flag); textBox.setVisible(!flag); save.setVisible(!flag); label.setVisible(flag);
}
@Override public void createGUI() {
// Simplified GUI setup (in full code, use JPanel, add components, set layout)
JFrame frame = new JFrame("Mediator Example - Note Dialog");
frame.setLayout(new BorderLayout());
frame.add(add, BorderLayout.NORTH);
frame.add(del, BorderLayout.NORTH);
frame.add(filter, BorderLayout.NORTH);
frame.add(list, BorderLayout.CENTER);
frame.add(title, BorderLayout.SOUTH);
frame.add(textBox, BorderLayout.SOUTH);
frame.add(save, BorderLayout.SOUTH);
frame.add(label, BorderLayout.SOUTH);
frame.setSize(400, 300);
frame.setVisible(true);
registerComponent(add); registerComponent(del); registerComponent(filter);
registerComponent(list); registerComponent(save); registerComponent(title);
registerComponent(textBox);
}
}
// Instantiation (main method)
package mediator.example;
public class Main {
public static void main(String[] args) {
Editor editor = new Editor();
editor.createGUI();
// Interact via GUI; e.g., clicking Add creates new note, handled by mediator
}
}
This Java implementation simulates a dialog box for note editing, where components like the list selection triggers updates to title and text box via the mediator, with exception handling for null selections during saves and marks. Event types could be modeled as an enum (e.g., enum EventType { ADD, DELETE, [SAVE](/page/Save), [FILTER](/page/Filter) }) to route actions in registerComponent. The system is instantiated by creating the Editor and calling createGUI(), enabling coordinated UI behavior without direct component coupling.[13]
Usage and Considerations
Applicability
The Mediator pattern is particularly applicable in systems exhibiting tight coupling among multiple objects or components, where direct communications create dependencies that hinder maintainability and reusability. It is recommended when a set of objects interact in complex, well-defined ways that cannot be easily encapsulated by other behavioral patterns, such as Observer or Strategy, allowing the extraction of interaction logic into a central mediator to isolate changes and promote loose coupling.[2] This approach is ideal for scenarios involving many-to-many interactions, where objects would otherwise require numerous subclasses or direct references to achieve varied behaviors in different contexts.[2]
In event-driven architectures, the pattern manifests as a mediator topology, where an event mediator orchestrates the flow of events across components, maintaining state, handling errors, and ensuring restart capabilities without broadcasting to the entire system.[14] For microservices orchestration, it is employed in the application layer to route commands to handlers, reducing coupling by centralizing request processing and enabling pipeline behaviors like validation or logging, as seen in implementations using libraries such as MediatR.[15] In user interface design, particularly with MVC frameworks, controllers often act as mediators to coordinate interactions between views and models, preventing direct dependencies and facilitating communication in GUI components.[16]
Contemporary applications extend to reactive programming paradigms, where mediators manage bidirectional communication between reactive streams and components, such as in RxJava-based systems for coordinating asynchronous event propagation without introducing tight coupling.[17] Similarly, in game development, the pattern supports coordination among game systems and UI elements by funneling interactions through a central mediator, which simplifies updates to behaviors in engines like Godot.[18]
The pattern's effectiveness relies on prerequisites such as existing high coupling in the system; it is not suitable for simple, one-to-one interactions where the overhead of a mediator would introduce unnecessary complexity.[2]
Advantages and Limitations
The Mediator pattern provides significant advantages in managing interactions among multiple objects. By centralizing communication through a dedicated mediator object, it reduces direct dependencies between collaborating objects, or colleagues, thereby promoting loose coupling and allowing each colleague to interact without explicit knowledge of others.[19] This decoupling simplifies the process of adding new colleagues, as they only need to interface with the mediator rather than being modified to accommodate existing ones, enhancing system flexibility and extensibility.[19] Furthermore, the pattern centralizes control logic, which streamlines object protocols—each colleague maintains a simple interface—and facilitates debugging by localizing changes to interaction behavior within the mediator subclass, rather than distributing them across multiple colleague classes.[19]
Despite these benefits, the Mediator pattern introduces several limitations that must be carefully managed. The centralization of interactions can cause the mediator to accumulate excessive responsibilities, potentially transforming it into a "god object" with bloated logic that is challenging to maintain and extend over time.[2] Additionally, as the sole coordinator of communications, the mediator represents a single point of failure; any malfunction or overload in the mediator disrupts interactions across all colleagues, increasing system vulnerability.[2] The added layer of indirection may also obscure the flow of control, making it harder for developers to trace and understand object interactions without thorough examination of the mediator's implementation.[3]
In practice, the Mediator pattern involves trade-offs between reducing coupling among colleagues and concentrating complexity within the mediator itself. To mitigate the risk of mediator bloat, large mediators can be decomposed into smaller, specialized sub-mediators or combined with other patterns like Facade for modular control, ensuring maintainability without sacrificing the pattern's core benefits.
Comparisons
The Mediator pattern often interacts with the Observer pattern, where the mediator can serve as a subject that notifies multiple colleagues of state changes, enabling one-to-many communication within the centralized coordination.[2] This synergy allows the Mediator to leverage Observer's subscription mechanism for dynamic event propagation without direct colleague dependencies.[3]
Similarly, the Facade pattern complements Mediator by providing a simplified interface to subsystems, much like Mediator's centralization of interactions, though Facade focuses on hiding complexity rather than managing peer communications.[2] The Command pattern pairs with Mediator when encapsulating requests as objects that the mediator routes to appropriate colleagues, facilitating indirect request handling and protocol implementation.[3]
Mediator can be combined with the Singleton pattern to ensure a single global instance manages all colleague interactions, promoting efficient resource use in scenarios requiring a unique coordination point.[2] Additionally, integrating the Strategy pattern allows for pluggable mediation logic, where different strategies define varying interaction rules without altering the core mediator structure.[3]
Introduced in the Gang of Four's seminal catalog of 23 design patterns, the Mediator pattern evolved alongside Observer, Facade, and Command to address object interaction challenges in object-oriented design, emphasizing loose coupling and independent variability among components.[20] This placement in the 1994 book filled gaps in reusable software elements by specifying how these patterns could interoperate for more flexible systems.[3]
Key Differences
The Mediator pattern contrasts with the Observer pattern primarily in the directionality and complexity of communication it supports. Observer implements a unidirectional publish-subscribe model, where subjects notify multiple observers of state changes through dynamic subscriptions, enabling loose one-way dependencies suitable for event broadcasting.[21] In contrast, Mediator facilitates bidirectional, complex message routing among multiple components via a central coordinator, eliminating direct dependencies and centralizing control to manage intricate interactions that Observer cannot efficiently handle without additional mechanisms.[2] Developers should select Observer for simple, runtime-configurable notifications where subscribers vary dynamically, but opt for Mediator in scenarios involving tightly coupled peers requiring coordinated, two-way orchestration to avoid a web of direct references.[3]
Unlike the Facade pattern, which simplifies client access to a subsystem by providing a unified interface without introducing new behavior or altering internal object communications, the Mediator pattern actively encapsulates and routes interactions between peer components, ensuring they remain ignorant of one another to promote decoupling.[22] Facade objects are typically unknown to subsystem elements, allowing direct peer communications within the subsystem, whereas Mediator components exclusively interact through the mediator, adding coordination logic that Facade lacks.[3] Use Facade to streamline external access to complex subsystems without modifying their internals, but choose Mediator when peer-to-peer communications need centralized management to reduce coupling in collaborative systems.[2]
The Proxy pattern differs from Mediator by focusing on indirection for a single target object, such as controlling access, caching, or lazy initialization, without coordinating multiple entities.[23] Mediator, however, centralizes complex interactions among numerous objects, routing requests bidirectionally to handle dependencies that Proxy addresses only for isolated access control.[2] Opt for Proxy in cases of simple, single-object indirection like remote access or virtualization, but employ Mediator when interaction complexity among peers demands a unified communication hub to prevent spaghetti-like dependencies.[3]
In modern concurrent systems, the Mediator pattern supports asynchronous message handling through centralized routing, enabling non-blocking coordination in event-driven architectures, unlike Observer's potential for fragmented callbacks or Facade's synchronous subsystem simplification, which may not scale to distributed async flows. This makes Mediator preferable for scalable, concurrent applications where async peer interactions require unified orchestration to maintain consistency without direct threading dependencies.[24]