Mediator Design Pattern

Mediator Design Pattern

18 Sep 2024
Intermediate
65.8K Views
34 min read
Learn with an interactive course and practical hands-on labs

⭐ .NET Design Patterns Course: Design Patterns in C# Online Training

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

Real-world Illustration of Mediator Design Pattern
In a busy restaurant, you don’t go directly to the kitchen to order your food. Instead, you use a waiter, who takes your order and passes it to the kitchen. The kitchen uses this system to avoid getting overwhelmed by direct requests from everyone.
Without the waiter, you would need to find the chef, explain your order, and it could disrupt the kitchen’s workflow. The waiter uses this role to keep things organized so you and the kitchen can focus on what you do best.

Mediator Design Pattern Basic Structure and Implementation

The structure for the implementation of the Mediator Design Pattern is given below:

Mediator Design Pattern - UML Diagram & Implementation

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:

Mediator Design Pattern - UML Diagram & Implementation

Explanation

Colleagues (Users)
  • 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.
Mediator (Chatroom Server)
  • The chatroom server works as a mediator.
  • Instead of sending messages directly to one another, users transmit them to the chatroom server.
Communication Flow
  • 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

Using the Mediator design when changing some classes is difficult because they are strongly connected to a large number of other classes.
  • 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.
Use the pattern when a component cannot be reused in another program due to its reliance on other components.
  • 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.
Use the Mediator when you find yourself writing a slew of component subclasses only to reuse some simple behavior in different circumstances.
  • 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.
When to Use the Mediator Design Pattern?
  • 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

Relationship with Other Patterns

1. Facade Pattern: The Mediator pattern is like the Facade pattern because it simplifies how things work together. But it is focused on managing interactions between objects.
2. Observer Pattern: It is different from the Observer pattern because it controls interactions directly instead of using notifications.
3. Command Pattern: The Mediator pattern works well with the Command pattern by routing commands through a single mediator, which helps manage how commands are sent and received.
4. Singleton Pattern: It is often used with the Singleton pattern to ensure there is only one mediator handling communication and keeping things organized.
Summary
The Mediator Design Pattern makes communication between objects easier by centralizing interactions within a mediator, eliminating dependencies, and encouraging loose coupling. This technique can help you handle complicated relationships, decouple components, and improve the system's reusability and manageability. These example shows how a mediator coordinates communication between colleague objects, thereby optimizing their interactions. For more information, see this Software Architecture and Design Certification Training.

FAQs

The primary goal of the Mediator Design Pattern is to centralize communication between various objects, reducing their reliance on one another. It reduces direct dependencies by having a mediator handle all interactions, resulting in loose coupling and easy maintenance.

As the Mediator Pattern matures, it can become complex and bloated, with too many tasks to handle. As a result, the central mediator may become a bottleneck in communication and control, leading to decreasing clarity and difficulty maintaining.

The Mediator Design Pattern encourages loose coupling by centralizing communication in the mediator, so that objects do not interact directly. This eliminates dependencies between objects, making the system more adaptable and easier to change without affecting other components.

Yes, the Mediator Design Pattern can facilitate communication among multiple objects. It is especially beneficial when multiple objects interact in various ways, as it simplifies communication logic by routing it through the mediator.

The Mediator Design Pattern is commonly used in GUI frameworks where many UI components (such as buttons and text fields) interact with one another. It is also widely used in chat systems, messaging queues, and network protocols to manage complicated communication flows.
Share Article
About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan, Founder and CEO of ScholarHat by DotNetTricks, is a renowned expert in System Design, Software Architecture, Azure Cloud, .NET, Angular, React, Node.js, Microservices, DevOps, and Cross-Platform Mobile App Development. His skill set extends into emerging fields like Data Science, Python, Azure AI/ML, and Generative AI, making him a well-rounded expert who bridges traditional development frameworks with cutting-edge advancements. Recognized as a Microsoft Most Valuable Professional (MVP) for an impressive 9 consecutive years (2016–2024), he has consistently demonstrated excellence in delivering impactful solutions and inspiring learners.

Shailendra’s unique, hands-on training programs and bestselling books have empowered thousands of professionals to excel in their careers and crack tough interviews. A visionary leader, he continues to revolutionize technology education with his innovative approach.
Accept cookies & close this