Introduction to the Mediator Pattern
The Mediator pattern is a behavioral design pattern that defines an object that encapsulates how a set of objects interact. This pattern promotes loose coupling by keeping objects from referring to each other explicitly and allows their interaction to be varied independently.
Key Concepts
- Mediator: An interface that defines the communication between colleague objects.
- Concrete Mediator: Implements the Mediator interface and coordinates communication between colleague objects.
- Colleague: Objects that communicate with each other through the mediator.
When to Use the Mediator Pattern
- When a set of objects communicate in complex but well-defined ways.
- When reusing an object is difficult because it refers to and communicates with many other objects.
- When a behavior that's distributed between several classes should be customizable without a lot of subclassing.
Example Scenario
Consider a chat room application where users can send messages to each other. Instead of each user object knowing about every other user object, a mediator (ChatRoom) can handle the communication.
UML Diagram
+---------------------+ +---------------------+ | Mediator |<------>| Colleague | |---------------------| |---------------------| | +sendMessage() | | +receiveMessage() | +---------------------+ +---------------------+ ^ ^ | | | | +---------------------+ +---------------------+ | Concrete Mediator | | User | |---------------------| |---------------------| | +sendMessage() | | +receiveMessage() | +---------------------+ +---------------------+
Code Example
Mediator Interface
public interface ChatMediator { void sendMessage(String message, User user); void addUser(User user); }
Concrete Mediator
import java.util.ArrayList; import java.util.List; public class ChatRoom implements ChatMediator { private List<User> users; public ChatRoom() { this.users = new ArrayList<>(); } @Override public void addUser(User user) { this.users.add(user); } @Override public void sendMessage(String message, User user) { for (User u : this.users) { // Message should not be received by the user sending it if (u != user) { u.receiveMessage(message); } } } }
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 sendMessage(String message); public abstract void receiveMessage(String message); }
Concrete Colleague
public class ChatUser extends User { public ChatUser(ChatMediator mediator, String name) { super(mediator, name); } @Override public void sendMessage(String message) { System.out.println(this.name + " Sending Message: " + message); mediator.sendMessage(message, this); } @Override public void receiveMessage(String message) { System.out.println(this.name + " Received Message: " + message); } }
Client Code
public class MediatorPatternDemo { public static void main(String[] args) { ChatMediator chatMediator = new ChatRoom(); User user1 = new ChatUser(chatMediator, "Alice"); User user2 = new ChatUser(chatMediator, "Bob"); User user3 = new ChatUser(chatMediator, "Charlie"); User user4 = new ChatUser(chatMediator, "Diana"); chatMediator.addUser(user1); chatMediator.addUser(user2); chatMediator.addUser(user3); chatMediator.addUser(user4); user1.sendMessage("Hello, everyone!"); } }
Explanation
- ChatMediator Interface: Defines the contract for sending messages and adding users.
- ChatRoom Class: Implements the ChatMediator interface and manages the communication between users.
- User Class: Abstract class representing a user in the chat room.
- ChatUser Class: Concrete implementation of the User class.
- MediatorPatternDemo Class: Demonstrates the use of the Mediator pattern.
Practical Exercise
Exercise
Create a simple traffic light system using the Mediator pattern. The system should have three lights (Red, Yellow, Green) and a TrafficMediator that controls the state transitions.
Solution
Mediator Interface
Concrete Mediator
public class TrafficController implements TrafficMediator { private TrafficLight redLight; private TrafficLight yellowLight; private TrafficLight greenLight; public TrafficController() { this.redLight = new RedLight(this); this.yellowLight = new YellowLight(this); this.greenLight = new GreenLight(this); } @Override public void changeLight(TrafficLight light) { if (light instanceof RedLight) { System.out.println("Red Light -> Green Light"); greenLight.turnOn(); } else if (light instanceof GreenLight) { System.out.println("Green Light -> Yellow Light"); yellowLight.turnOn(); } else if (light instanceof YellowLight) { System.out.println("Yellow Light -> Red Light"); redLight.turnOn(); } } public void start() { redLight.turnOn(); } }
Colleague
public abstract class TrafficLight { protected TrafficMediator mediator; public TrafficLight(TrafficMediator mediator) { this.mediator = mediator; } public abstract void turnOn(); }
Concrete Colleagues
public class RedLight extends TrafficLight { public RedLight(TrafficMediator mediator) { super(mediator); } @Override public void turnOn() { System.out.println("Red Light is ON"); mediator.changeLight(this); } } public class YellowLight extends TrafficLight { public YellowLight(TrafficMediator mediator) { super(mediator); } @Override public void turnOn() { System.out.println("Yellow Light is ON"); mediator.changeLight(this); } } public class GreenLight extends TrafficLight { public GreenLight(TrafficMediator mediator) { super(mediator); } @Override public void turnOn() { System.out.println("Green Light is ON"); mediator.changeLight(this); } }
Client Code
public class TrafficLightDemo { public static void main(String[] args) { TrafficController controller = new TrafficController(); controller.start(); } }
Explanation
- TrafficMediator Interface: Defines the contract for changing lights.
- TrafficController Class: Implements the TrafficMediator interface and manages the state transitions of the traffic lights.
- TrafficLight Class: Abstract class representing a traffic light.
- RedLight, YellowLight, GreenLight Classes: Concrete implementations of the TrafficLight class.
- TrafficLightDemo Class: Demonstrates the use of the Mediator pattern in a traffic light system.
Common Mistakes and Tips
- Direct Communication: Avoid direct communication between colleague objects. Always use the mediator for communication.
- Complex Logic in Colleagues: Keep the logic in colleague objects simple. The mediator should handle the complex interactions.
- Overuse: Do not overuse the Mediator pattern. It is best suited for complex interactions that are difficult to manage otherwise.
Summary
The Mediator pattern helps in reducing the complexity of communication between multiple objects by centralizing the communication logic in a mediator object. This promotes loose coupling and makes the system easier to maintain and extend. By following the examples and exercises provided, you should now have a solid understanding of how to implement and use the Mediator pattern in your own projects.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What are Design Patterns?
- History and Origin of Design Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
Module 3: Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Module 5: Application of Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring Using Design Patterns
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Design Patterns in Agile Development