21
NovMediator Design Pattern
Understanding Mediator Design Pattern
Understanding Mediator Design Patterns is a key concept in software design. It allows you to decrease the complexity of communication between several objects by introducing a mediator object to handle interactions. This results in a clearer and simpler communication interface, making the system easier to comprehend and manage.
In this design pattern tutorial, we will discuss the Mediator Design Pattern, including the question "What is the Mediator Design Pattern?"and "When to use the Mediator Design Pattern?" We'll also see examples of the Mediator Design Pattern in action. So, let us begin by addressing "What is the Mediator Design Pattern?"
What is a Mediator Design Pattern?
- The Mediator Design Pattern is a behavioral design pattern that allows objects to communicate with one another by centralizing interactions in a mediator object.
- Instead of directly interacting with one other, objects communicate via the mediator, which manages and coordinates their behaviors.
- This minimizes dependencies and promotes loose connectivity between components.
Real-world Illustration of Mediator Design Pattern
Mediator Design Pattern Basic Structure and Implementation
The structure for the implementation of the Mediator Design Pattern is given below:
The classes, interfaces, and objects in the above structure as follows:
- Mediator: An interface that defines operations that enable colleagues to communicate with one another via the mediator.
- ConcreteMediator: A class that implements the Mediator interface, coordinating and communicating with other objects.
- Colleague: A class that contains a protected field with a reference to a mediator, allowing for indirect communication.
- ConcreteColleagueA/B: Classes that communicate with one another through a mediator rather than directly.
Real Life Example:
Explanation
- Each person in the chatroom is considered a colleague.
- They wish to send and receive messages without having to handle direct connections with all other users.
- The chatroom server works as a mediator.
- Instead of sending messages directly to one another, users transmit them to the chatroom server.
- If a person wants to send a message to another user, they do so through the chatroom server.
- The chatroom server recognizes the intended recipient and routes the message appropriately.
- The recipient receives the message from the chatroom server.
This indirect communication ensures that users do not have to handle individual connections with other users.The chatroom server manages all message routing, which simplifies the procedure and reduces the possibility of errors or missed messages.The chatroom server facilitates communication, allowing participants to interact and talk without the complexities of direct peer-to-peer connection.
Let's explore this concept in different languages, such asC# Compiler,Java Compiler,Python Compiler,TypeScript Compiler, and JavaScript Compiler.Example
using System;
using System.Collections.Generic;
// Mediator Interface
public interface IChatroom
{
void SendMessage(string message, User user);
void AddUser(User user);
}
// Concrete Mediator
public class Chatroom : IChatroom
{
private Dictionary<string, User> users = new Dictionary<string, User>();
public void SendMessage(string message, User user)
{
if (users.TryGetValue(user.GetRecipient(), out User recipient))
{
recipient.ReceiveMessage(message);
}
else
{
Console.WriteLine("User not found: " + user.GetRecipient());
}
}
public void AddUser(User user)
{
users[user.GetName()] = user;
}
}
// Colleague
public abstract class User
{
protected IChatroom chatroom;
protected string name;
protected string recipient;
public User(IChatroom chatroom, string name)
{
this.chatroom = chatroom;
this.name = name;
}
public string GetName()
{
return name;
}
public void SetRecipient(string recipient)
{
this.recipient = recipient;
}
public string GetRecipient()
{
return recipient;
}
public abstract void SendMessage(string message);
public abstract void ReceiveMessage(string message);
}
// Concrete Colleague
public class UserImpl : User
{
public UserImpl(IChatroom chatroom, string name) : base(chatroom, name)
{
chatroom.AddUser(this);
}
public override void SendMessage(string message)
{
Console.WriteLine(name + " sends: " + message);
chatroom.SendMessage(message, this);
}
public override void ReceiveMessage(string message)
{
Console.WriteLine(name + " receives: " + message);
}
}
// Main Class
public class MediatorPatternDemo
{
public static void Main(string[] args)
{
IChatroom chatroom = new Chatroom();
User alice = new UserImpl(chatroom, "Alice");
User bob = new UserImpl(chatroom, "Bob");
alice.SetRecipient("Bob");
bob.SetRecipient("Alice");
alice.SendMessage("Hello Bob!");
bob.SendMessage("Hi Alice!");
}
}
import java.util.Map;
import java.util.HashMap;
// Mediator Interface
interface Chatroom {
void sendMessage(String message, User user);
void addUser(User user);
}
// Concrete Mediator
class ChatroomImpl implements Chatroom {
private Map<String, User> users = new HashMap<>();
@Override
public void sendMessage(String message, User user) {
User recipient = users.get(user.getRecipient());
if (recipient != null) {
recipient.receiveMessage(message);
} else {
System.out.println("User not found: " + user.getRecipient());
}
}
@Override
public void addUser(User user) {
users.put(user.getName(), user);
}
}
// Colleague
abstract class User {
protected Chatroom chatroom;
protected String name;
protected String recipient;
public User(Chatroom chatroom, String name) {
this.chatroom = chatroom;
this.name = name;
}
public String getName() {
return name;
}
public void setRecipient(String recipient) {
this.recipient = recipient;
}
public String getRecipient() {
return recipient;
}
public abstract void sendMessage(String message);
public abstract void receiveMessage(String message);
}
// Concrete Colleague
class UserImpl extends User {
public UserImpl(Chatroom chatroom, String name) {
super(chatroom, name);
chatroom.addUser(this);
}
@Override
public void sendMessage(String message) {
System.out.println(name + " sends: " + message);
chatroom.sendMessage(message, this);
}
@Override
public void receiveMessage(String message) {
System.out.println(name + " receives: " + message);
}
}
// Main Class
public class MediatorPatternDemo {
public static void main(String[] args) {
Chatroom chatroom = new ChatroomImpl();
User alice = new UserImpl(chatroom, "Alice");
User bob = new UserImpl(chatroom, "Bob");
alice.setRecipient("Bob");
bob.setRecipient("Alice");
alice.sendMessage("Hello Bob!");
bob.sendMessage("Hi Alice!");
}
}
# Mediator Interface
class Chatroom:
def send_message(self, message, user):
pass
def add_user(self, user):
pass
# Concrete Mediator
class ChatroomImpl(Chatroom):
def __init__(self):
self.users = {}
def send_message(self, message, user):
recipient = self.users.get(user.get_recipient())
if recipient:
recipient.receive_message(message)
else:
print(f"User not found: {user.get_recipient()}")
def add_user(self, user):
self.users[user.get_name()] = user
# Colleague
class User:
def __init__(self, chatroom, name):
self.chatroom = chatroom
self.name = name
self.recipient = None
def get_name(self):
return self.name
def set_recipient(self, recipient):
self.recipient = recipient
def get_recipient(self):
return self.recipient
def send_message(self, message):
pass
def receive_message(self, message):
pass
# Concrete Colleague
class UserImpl(User):
def __init__(self, chatroom, name):
super().__init__(chatroom, name)
chatroom.add_user(self)
def send_message(self, message):
print(f"{self.name} sends: {message}")
self.chatroom.send_message(message, self)
def receive_message(self, message):
print(f"{self.name} receives: {message}")
# Main Function
def main():
chatroom = ChatroomImpl()
alice = UserImpl(chatroom, "Alice")
bob = UserImpl(chatroom, "Bob")
alice.set_recipient("Bob")
bob.set_recipient("Alice")
alice.send_message("Hello Bob!")
bob.send_message("Hi Alice!")
if __name__ == "__main__":
main()
// Mediator Interface
interface Chatroom {
sendMessage(message: string, user: User): void;
addUser(user: User): void;
}
// Concrete Mediator
class ChatroomImpl implements Chatroom {
private users: Map = new Map();
sendMessage(message: string, user: User): void {
const recipient = this.users.get(user.getRecipient());
if (recipient) {
recipient.receiveMessage(message);
} else {
console.log("User not found: " + user.getRecipient());
}
}
addUser(user: User): void {
this.users.set(user.getName(), user);
}
}
// Colleague
abstract class User {
protected chatroom: Chatroom;
protected name: string;
protected recipient: string;
constructor(chatroom: Chatroom, name: string) {
this.chatroom = chatroom;
this.name = name;
}
getName(): string {
return this.name;
}
setRecipient(recipient: string): void {
this.recipient = recipient;
}
getRecipient(): string {
return this.recipient;
}
abstract sendMessage(message: string): void;
abstract receiveMessage(message: string): void;
}
// Concrete Colleague
class UserImpl extends User {
constructor(chatroom: Chatroom, name: string) {
super(chatroom, name);
chatroom.addUser(this);
}
sendMessage(message: string): void {
console.log(this.name + " sends: " + message);
this.chatroom.sendMessage(message, this);
}
receiveMessage(message: string): void {
console.log(this.name + " receives: " + message);
}
}
// Main function
function main() {
const chatroom = new ChatroomImpl();
const alice = new UserImpl(chatroom, "Alice");
const bob = new UserImpl(chatroom, "Bob");
alice.setRecipient("Bob");
bob.setRecipient("Alice");
alice.sendMessage("Hello Bob!");
bob.sendMessage("Hi Alice!");
}
main();
// Mediator Interface
class Chatroom {
sendMessage(message, user) {}
addUser(user) {}
}
// Concrete Mediator
class ChatroomImpl extends Chatroom {
constructor() {
super();
this.users = new Map();
}
sendMessage(message, user) {
const recipient = this.users.get(user.getRecipient());
if (recipient) {
recipient.receiveMessage(message);
} else {
console.log("User not found: " + user.getRecipient());
}
}
addUser(user) {
this.users.set(user.getName(), user);
}
}
// Colleague
class User {
constructor(chatroom, name) {
this.chatroom = chatroom;
this.name = name;
this.recipient = "";
}
getName() {
return this.name;
}
setRecipient(recipient) {
this.recipient = recipient;
}
getRecipient() {
return this.recipient;
}
sendMessage(message) {}
receiveMessage(message) {}
}
// Concrete Colleague
class UserImpl extends User {
constructor(chatroom, name) {
super(chatroom, name);
chatroom.addUser(this);
}
sendMessage(message) {
console.log(this.name + " sends: " + message);
this.chatroom.sendMessage(message, this);
}
receiveMessage(message) {
console.log(this.name + " receives: " + message);
}
}
// Main function
function main() {
const chatroom = new ChatroomImpl();
const alice = new UserImpl(chatroom, "Alice");
const bob = new UserImpl(chatroom, "Bob");
alice.setRecipient("Bob");
bob.setRecipient("Alice");
alice.sendMessage("Hello Bob!");
bob.sendMessage("Hi Alice!");
}
main();
Output
Alice sends: Hello Bob!
Bob receives: Hello Bob!
Bob sends: Hi Alice!
Alice receives: Hi Alice!
Explanation
- This example demonstrates the Mediator Design Pattern, where a central mediator (Chatroom) manages communication between different objects (User).
- Each user sends and receives messages through the mediator, decoupling the direct communication between them and ensuring that interaction is controlled and centralized.
Applications of Mediator Design Pattern
- The pattern allows you to extract all of the relationships between classes into a single class, isolating any changes to one component from the others.
- After using the Mediator, individual components become oblivious to the other components.
- They might still speak with one another, although indirectly, via a mediator item. To reuse a component in another app, you must give it a new mediator class.
- Because all interactions between components occur within the mediator, it is simple to establish entirely new ways for these components to collaborate by adding new mediator classeswithout changing the components themselves.
Benefits of Mediator Design Pattern
- It separates the number of classes.
- It streamlines object protocols.
- It centralizes control.
- Because the separate components no longer need to communicate with one another, they become simpler and easier to manage.
- The components are more generic because they do not require logic to handle intercommunication.
- Complex interactions: When you have a collection of items that communicate in sophisticated ways, they form a tangled web of dependencies. The mediator streamlines these interactions by centralizing communication logic.
- Decoupling Components: When you need to remove direct dependencies between components, the system becomes more modular and easier to manage.
- Reusability: When you wish to reuse objects without having to modify them to fit into new interactions, the mediator pattern decouples them, allowing them to be reused in many contexts.
- Simplifying Object Protocols: When an object's behavior is dependent on a large number of other objects, the communication protocol should be simplified.
- Managing State Changes: When state changes in one object must be propagated to others, managing these updates directly becomes complex.
When not to use the Mediator Design Pattern?
- Simple Interactions: The interactions between components are clear, and using a mediator pattern would add unneeded complexity.
- Single Responsibility Principle (SRP): Each component has a single responsibility, so using a mediator pattern may break the Single Responsibility Principle, resulting in less maintainable code.
- Performance Concerns: Using a mediator pattern may result in performance overhead, particularly in circumstances when direct communication between components is more efficient.
- Small-Scale Applications: In small-scale applications with a few components, the costs of installing a mediator pattern may exceed the benefits.
- Over-Engineering: Avoid utilizing the Mediator pattern if it appears to be an overly complex solution for your system's specific requirements. Always examine the trade-offs and the specific requirements of your application.
Read More Articles Related to Design patterns