Mediator Pattern
title: “Mediator Pattern” date: 2024-07-14 categories: - Design Patterns tags: - design patterns - behavioral patterns - mediator - architecture bookHidden: false
The Mediator pattern steps in when objects are talking to each other so much that your code starts to look like a group chat gone wrong.
It’s a behavioral pattern that centralizes communication between objects. Instead of every component knowing about every other component, they all talk through a mediator, which coordinates interactions and keeps dependencies under control.
Problem and Solution
Problem
Suppose you’re designing a chat application where multiple users can send messages to each other. Without a centralized way to manage communication, each user would need to be aware of all other users, resulting in tightly coupled code that is difficult to maintain and extend. Adding new users or changing communication behavior would require modifying all related classes.
Solution
The Mediator pattern solves this problem by creating a mediator object (e.g., ChatRoom) that manages the communication between users. Each user communicates only with the mediator, which then relays messages to the appropriate recipients. This setup decouples users from each other, making the system easier to maintain, modify, and extend.
Structure
The Mediator pattern typically includes:
- Mediator Interface: Defines methods for communication between colleagues.
- Concrete Mediator: Implements the mediator interface, coordinating interactions between colleagues.
- Colleague Interface: Defines a common interface for all participants that communicate through the mediator.
- Concrete Colleagues: Implement the colleague interface and interact with each other through the mediator.
UML Diagram
+-------------------+ +-----------------------+
| Mediator |<---------| ConcreteMediator |
|-------------------| |-----------------------|
| + sendMessage() | | + sendMessage() |
+-------------------+ +-----------------------+
^
|
+-------------------+ +-----------------------+
| Colleague |<---------| ConcreteColleague |
|-------------------| |-----------------------|
| + send() | | + send() |
| + receive() | | + receive() |
+-------------------+ +-----------------------+Example: Chat Application
Let’s implement a simple chat application using the Mediator pattern. We’ll create a ChatRoom mediator to handle communication between users, allowing them to send messages to each other without being directly aware of one another.
Step 1: Define the Mediator Interface
The ChatMediator interface defines a method for sending messages. The mediator will coordinate message delivery between users.
// Mediator Interface
interface ChatMediator {
void sendMessage(String message, User user);
}Step 2: Implement the Concrete Mediator
The ChatRoom class implements the ChatMediator interface, managing a list of users and relaying messages between them.
// Concrete Mediator
class ChatRoom implements ChatMediator {
private List<User> users = new ArrayList<>();
public void addUser(User user) {
users.add(user);
}
@Override
public void sendMessage(String message, User user) {
for (User u : users) {
if (u != user) {
u.receive(message);
}
}
}
}Step 3: Define the Colleague Interface
The User class represents a participant in the chat. Each user has methods to send and receive messages and interacts with others through the ChatRoom.
// Colleague Interface
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 4: Implement Concrete Colleagues
Each ConcreteUser class represents a specific user in the chat and interacts only through the mediator.
// Concrete Colleague
class ConcreteUser extends User {
public ConcreteUser(ChatMediator mediator, String name) {
super(mediator, name);
}
@Override
public void send(String message) {
System.out.println(this.name + " sends: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println(this.name + " received: " + message);
}
}Step 5: Client Code Using the Mediator
The client code creates users and a chat room, adding users to the chat room and allowing them to send messages.
public class Client {
public static void main(String[] args) {
ChatMediator chatRoom = new ChatRoom();
User user1 = new ConcreteUser(chatRoom, "Alice");
User user2 = new ConcreteUser(chatRoom, "Bob");
User user3 = new ConcreteUser(chatRoom, "Charlie");
((ChatRoom) chatRoom).addUser(user1);
((ChatRoom) chatRoom).addUser(user2);
((ChatRoom) chatRoom).addUser(user3);
user1.send("Hello, everyone!");
user2.send("Hi, Alice!");
}
}Output
Alice sends: Hello, everyone!
Bob received: Hello, everyone!
Charlie received: Hello, everyone!
Bob sends: Hi, Alice!
Alice received: Hi, Alice!
Charlie received: Hi, Alice!In this example:
- The
ChatRoommediator handles communication between users, with each user sending and receiving messages only through the mediator. ConcreteUserrepresents a user in the chat, sending and receiving messages through theChatRoom.- The client code interacts with the users through the mediator, which manages all message delivery.
Applicability
Use the Mediator pattern when:
- You need to reduce coupling between multiple classes that communicate with each other.
- You want to simplify complex communication patterns or workflows by centralizing interactions.
- You need flexibility to modify communication between objects without modifying the objects themselves.
Advantages and Disadvantages
Advantages
- Reduces Coupling: The Mediator pattern reduces direct dependencies between objects, promoting loose coupling and improving maintainability.
- Centralized Control: By managing interactions in one place, the mediator simplifies complex communication and makes it easier to understand and modify.
- Enhanced Flexibility: New colleagues can be added or removed easily, and communication behavior can be changed by modifying the mediator.
Disadvantages
- Increased Complexity in the Mediator: As more communication logic is centralized, the mediator can become complex, potentially turning into a “god object.”
- Potential Performance Bottleneck: Since all interactions go through the mediator, it can become a performance bottleneck, especially in high-traffic scenarios.
Best Practices for Implementing the Mediator Pattern
- Limit Mediator Complexity: Avoid turning the mediator into an overly complex object. If the mediator becomes too complex, consider splitting its responsibilities.
- Use Mediator to Manage High Interdependencies: Apply the Mediator pattern in systems where multiple objects interact frequently, as it simplifies managing these dependencies.
- Encapsulate Communication Patterns: Use the mediator to encapsulate communication patterns, making it easier to modify behavior without affecting the colleagues.
Conclusion
The Mediator pattern provides a centralized way to manage communication between objects, reducing coupling and making the system easier to extend and maintain. By isolating communication logic, the Mediator pattern enhances flexibility and scalability in complex systems.