The Mediator Pattern is a behavioral design pattern that facilitates communication between objects in a software system. It centralizes complex communications and control logic between related objects, promoting loose coupling and making it easier to modify their interactions independently.
In a typical software system, objects often need to communicate with each other to accomplish tasks. As systems grow, these interactions can become complex, leading to tightly coupled objects that are difficult to maintain and extend. The Mediator Pattern addresses this problem by introducing a mediator object that handles all communication between different objects, thereby reducing direct dependencies between them.
Important Components
- Mediator: An interface or abstract class defining methods for communication between colleague objects.
- Concrete Mediator: Implements the Mediator interface, coordinating interactions between colleague objects by maintaining references to them.
- Colleague: A class that interacts with other colleagues through the mediator, rather than directly. Colleagues are aware of the mediator but not of each other.
- Client: Creates the mediator and colleague objects, sets up their relationships, and initiates interactions.
How It Works
- The Mediator defines the interface for communication.
- The Concrete Mediator implements the logic to handle interactions, often storing references to all colleague objects.
- Colleague objects send messages to the mediator when they need to communicate, and the mediator decides how to route or process these messages.
- The Client configures the system by associating colleagues with the mediator.
- This centralizes communication logic, reducing direct dependencies between colleagues and making the system easier to maintain or extend.
Sample Implementation
// Mediator Interface
interface ChatMediator {
void sendMessage(String message, User user);
void addUser(User user);
}
// Concrete Mediator
class ChatRoom implements ChatMediator {
private List<User> users;
public ChatRoom() {
this.users = new ArrayList<>();
}
@Override
public void addUser(User user) {
users.add(user);
System.out.println(user.getName() + " has joined the chat room.");
}
@Override
public void sendMessage(String message, User sender) {
System.out.println(sender.getName() + " sends: " + message);
// Broadcast message to all users except the sender
for (User user : users) {
if (user != sender) {
user.receiveMessage(message);
}
}
}
}
// Colleague
abstract class User {
protected ChatMediator mediator;
protected String name;
public User(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public String getName() {
return name;
}
public abstract void sendMessage(String message);
public abstract void receiveMessage(String message);
}
// Concrete Colleague
class ChatUser extends User {
public ChatUser(ChatMediator mediator, String name) {
super(mediator, name);
}
@Override
public void sendMessage(String message) {
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println(name + " received: " + message);
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Create mediator
ChatMediator chatRoom = new ChatRoom();
// Create users (colleagues)
User alice = new ChatUser(chatRoom, "Alice");
User bob = new ChatUser(chatRoom, "Bob");
User charlie = new ChatUser(chatRoom, "Charlie");
// Add users to the chat room
chatRoom.addUser();
chatRoom.addUser(bob);
chatRoom.addUser(charlie);
// Users send messages
alice.sendMessage("Hi everyone!");
bob.sendMessage("Hey Alice, what's up?");
}
}// Mediator Interface
interface ChatMediator {
void sendMessage(String message, User user);
void addUser(User user);
}
// Concrete Mediator
class ChatRoom implements ChatMediator {
private List<User> users;
public ChatRoom() {
this.users = new ArrayList<>();
}
@Override
public void addUser(User user) {
users.add(user);
System.out.println(user.getName() + " has joined the chat room.");
}
@Override
public void sendMessage(String message, User sender) {
System.out.println(sender.getName() + " sends: " + message);
// Broadcast message to all users except the sender
for (User user : users) {
if (user != sender) {
user.receiveMessage(message);
}
}
}
}
// Colleague
abstract class User {
protected ChatMediator mediator;
protected String name;
public User(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public String getName() {
return name;
}
public abstract void sendMessage(String message);
public abstract void receiveMessage(String message);
}
// Concrete Colleague
class ChatUser extends User {
public ChatUser(ChatMediator mediator, String name) {
super(mediator, name);
}
@Override
public void sendMessage(String message) {
mediator.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println(name + " received: " + message);
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Create mediator
ChatMediator chatRoom = new ChatRoom();
// Create users (colleagues) with updated names
User paani = new ChatUser(chatRoom, "Paani");
User mahesh = new ChatUser(chatRoom, "Mahesh");
User datta = new ChatUser(chatRoom, "Datta");
// Add users to the chat room
chatRoom.addUser(paani);
chatRoom.addUser(mahesh);
chatRoom.addUser(datta);
// Users send messages
paani.sendMessage("Hi everyone!");
mahesh.sendMessage("Hey Paani, what's up?");
}
}
/*
Paani has joined the chat room.
Mahesh has joined the chat room.
Datta has joined the chat room.
Paani sends: Hi everyone!
Mahesh received: Hi everyone!
Datta received: Hi everyone!
Mahesh sends: Hey Paani, what's up?
Paani received: Hey Paani, what's up?
Datta received: Hey Paani, what's up?
*/
Code language: JavaScript (javascript)
Use case and Implementation
Managing interactions between various banking services (e.g., account services, loan services, transaction processing) through a central mediator.
//MediatorPatternDemo.java // Define the Mediator Interface: interface BankingMediator { void notify(Object sender, String event); } // Create Concrete Mediator class ConcreteBankingMediator implements BankingMediator { private AccountService accountService; private LoanService loanService; private TransactionService transactionService; public void setAccountService(AccountService accountService) { this.accountService = accountService; } public void setLoanService(LoanService loanService) { this.loanService = loanService; } public void setTransactionService(TransactionService transactionService) { this.transactionService = transactionService; } @Override public void notify(Object sender, String event) { if (event.equals("createAccount")) { transactionService.processAccountCreation(); } else if (event.equals("applyLoan")) { accountService.checkEligibility(); loanService.processLoanApplication(); } else if (event.equals("processTransaction")) { accountService.updateAccount(); // We need to remove the call to the mediator here to prevent recursion transactionService.finalizeTransaction(); } } } // Create Colleague Classes class AccountService { private BankingMediator mediator; public AccountService(BankingMediator mediator) { this.mediator = mediator; } public void createAccount() { System.out.println("AccountService: Creating a new account."); mediator.notify(this, "createAccount"); } public void checkEligibility() { System.out.println("AccountService: Checking account eligibility."); } public void updateAccount() { System.out.println("AccountService: Updating account information."); } } class LoanService { private BankingMediator mediator; public LoanService(BankingMediator mediator) { this.mediator = mediator; } public void applyLoan() { System.out.println("LoanService: Applying for a loan."); mediator.notify(this, "applyLoan"); } public void processLoanApplication() { System.out.println("LoanService: Processing loan application."); } } class TransactionService { private BankingMediator mediator; public TransactionService(BankingMediator mediator) { this.mediator = mediator; } public void processTransaction() { System.out.println("TransactionService: Processing transaction."); mediator.notify(this, "processTransaction"); } // This method doesn't call the mediator again, avoiding infinite loop public void finalizeTransaction() { System.out.println("TransactionService: Finalizing transaction."); } public void processAccountCreation() { System.out.println("TransactionService: Processing account creation transaction."); } } // Implementing the Mediator public class MediatorPatternDemo { public static void main(String[] args) { ConcreteBankingMediator mediator = new ConcreteBankingMediator(); AccountService accountService = new AccountService(mediator); LoanService loanService = new LoanService(mediator); TransactionService transactionService = new TransactionService(mediator); mediator.setAccountService(accountService); mediator.setLoanService(loanService); mediator.setTransactionService(transactionService); // Creating an account accountService.createAccount(); // Applying for a loan loanService.applyLoan(); // Processing a transaction transactionService.processTransaction(); } } /* C:\>javac MediatorPatternDemo.java C:\>java MediatorPatternDemo AccountService: Creating a new account. TransactionService: Processing account creation transaction. LoanService: Applying for a loan. AccountService: Checking account eligibility. LoanService: Processing loan application. TransactionService: Processing transaction. AccountService: Updating account information. TransactionService: Finalizing transaction. */
Pros
- Loose Coupling: Colleagues don’t need to know about each other, reducing dependencies.
- Centralized Control: The mediator encapsulates interaction logic, making it easier to manage and modify.
- Reusability: Colleagues can be reused in different contexts with different mediators.
- Simplified Maintenance: Changes to interaction logic are confined to the mediator, not scattered across colleagues.
Cons
- Mediator Complexity: The mediator can become a “god object” if it handles too many responsibilities.
- Performance Overhead: Centralizing communication may introduce bottlenecks in large systems.
- Single Point of Failure: The mediator is critical, and issues in it can affect the entire system.
- Increased Abstraction: Adds an extra layer, which may complicate simple systems.
When to Use
- When multiple objects need to interact in complex ways, and direct communication would lead to tight coupling.
- When you want to centralize control of interactions between objects.
- When you need to simplify communication logic in a system with many interdependent components.
- When you want to support dynamic or reusable colleague interactions.
Real-World Example
- Chat Applications: A chat server mediates messages between users, as in the example above.
- Air Traffic Control: A control tower (mediator) coordinates communication between planes (colleagues) to prevent collisions.
- GUI Frameworks: A form or dialog (mediator) manages interactions between UI components (e.g., buttons, text fields).
- Event Bus Systems: An event bus mediates events between publishers and subscribers in a publish-subscribe system.
The Mediator Pattern is a powerful tool for managing complex interactions between objects in a software system. By centralizing communication and control logic in a mediator, it promotes loose coupling, simplifies object interactions, and enhances system flexibility. However, it is essential to manage the complexity of the mediator itself to avoid creating a monolithic component that is difficult to maintain. When applied correctly, the Mediator Pattern can significantly improve the modularity and maintainability of a software system.