Mediator Pattern in Java
The **Mediator Pattern** is a behavioral design pattern in Java that aims to reduce direct dependencies between multiple objects by introducing a mediator object. Instead of allowing objects to communicate directly with each other, they interact through a central mediator, which coordinates their actions. This promotes loose coupling, making the system easier to maintain and extend. By encapsulating interaction logic within the mediator, the pattern helps avoid a tangled web of object references, simplifying complex communication scenarios.
The primary purpose of the Mediator Pattern is to decouple components that would otherwise need to reference each other directly. In systems where multiple classes interact in intricate ways, direct communication can lead to rigid, hard-to-modify code. The mediator acts as a middleman, handling requests and delegating tasks, ensuring that changes in one component don’t require modifications in others. This is particularly useful in GUI applications, chat systems, or workflow management, where objects need to notify each other without creating tight dependencies.
Another key benefit of the Mediator Pattern is
centralized control[1]. By consolidating communication logic in a single mediator class, the pattern makes it easier to manage and debug interactions between objects. For example, in a multi-user chat application, the mediator can handle message routing, user joins, and leaves, rather than having each user object track all others. This reduces redundancy and ensures consistency, as all interaction rules are enforced in one place.
Finally, the Mediator Pattern enhances **scalability and flexibility**. Since objects only depend on the mediator, adding new components or modifying existing ones becomes simpler. The mediator can be extended or replaced without affecting individual objects, making the system more adaptable to change. In Java, this pattern is often implemented using interfaces to define mediator contracts, allowing different mediators to be swapped as needed. Overall, the Mediator Pattern is a powerful tool for managing complex object interactions while maintaining clean, modular code.
Chatroom Application using the Mediator Pattern
Here we will use the Mediator pattern in the context of a chatroom application.
First we will define an interface for our mediator
//Mediator interface
package com.java.behavioral.mediator;
public interface Mediator{
public void send(String message, Colleague colleague);
}
While we described the Colleague as an interface above, it is more useful to use an abstract class in this case:
//Colleague interface
package com.java.behavioral.mediator;
public abstract class Colleague {
private Mediator mediator;
public Colleague(Mediator m) {
mediator = m;
}
// send a message via the mediator
public void send(String message) {
mediator.send(message, this);
}
// get access to the mediator
public Mediator getMediator() {
return mediator;
}
public abstract void receive(String message);
}
Now let's create our concrete mediator implementation
package com.java.behavioral.mediator;
import java.util.*;
public class ApplicationMediator implements Mediator {
private ArrayList<Colleague> colleagues;
public ApplicationMediator() {
colleagues = new ArrayList<Colleague>();
}
public void addColleague(Colleague colleague) {
colleagues.add(colleague);
}
public void send(String message, Colleague originator) {
/* let all other screens know that this screen has changed */
for (Colleague colleague : colleagues) {
if (colleague != originator) {
colleague.receive(message);
}
}
}
}
Finally we will create one concrete colleage.
package com.java.behavioral.mediator;
public class ConcreteColleague extends Colleague {
public ConcreteColleague(Mediator m) {
super(m);
// TODO Auto-generated constructor stub
}
public void receive(String message) {
System.out.println("Colleague Received: " + message);
}
}
If we assume that we could have many different colleagues that react differently when a message is received, this pattern fits in well.
For example, we could have a mobileColleague that needs to display the message differently to the desktop colleague.
package com.java.behavioral.mediator;
public class MobileColleague extends Colleague {
public MobileColleague(Mediator m) {
super(m);
// TODO Auto-generated constructor stub
}
public void receive(String message) {
System.out.println("Mobile Received: " + message);
}
}
Here's a client that drives the entire application:
package com.java.behavioral.mediator;
public class Client {
public static void main(String[] args) {
ApplicationMediator mediator = new ApplicationMediator();
ConcreteColleague desktop = new ConcreteColleague(mediator);
MobileColleague mobile = new MobileColleague(mediator);
mediator.addColleague(desktop);
mediator.addColleague(mobile);
desktop.send("Hello World");
mobile.send("Hello");
}
}
Advantages of the Mediator Pattern
Mediators improve code organization in many ways: reducing subclassing, decoupling subsystems, and simplifying messaging systems and protocols.
Disadvantages of the Mediator Pattern
- Complexity can creep into a Mediator over time as you customize it for new applications. You have missed the point if you allow a Mediator to become too complex. Several Mediators tailored for specific applications can help. Be careful not to add back the complexity you are trying to eliminate.
- A mediator class can exceed its original intent if you are not careful. A good OO program is a network of cooperating agents.
Focus your mediators on doing one thing only.
[1]
centralized control: Centralized control in creational patterns refers to the strategy of managing object instantiation logic in a specific, well-defined part of the system. This approach promotes better organization and flexibility by decoupling the object creation process from the direct client code, making it easier to modify or extend object creation without affecting other parts of the application.