Decoupling Communication with the Mediator Pattern

Simplify interactions between components with a centralized controller

Posted by Hüseyin Sekmenoğlu on September 26, 2020 Design Patterns

๐Ÿง  What Is the Mediator Pattern?

The Mediator Pattern is a behavioral design pattern that promotes loose coupling by centralizing complex communication between objects into a single mediator. Instead of having components refer to each other directly, they interact through a mediator, making the system easier to manage and extend.


๐Ÿ”„ When to Use It

Use the Mediator Pattern when:

  • Multiple objects interact in complex ways

  • You want to avoid tightly coupled code with lots of interdependencies

  • Adding or modifying interactions between classes is becoming error-prone

  • You need better control over how objects communicate

Common scenarios:

  • UI components (buttons, text boxes, dialogs)

  • Chat applications

  • Workflow engines or pipelines

  • Microservices communication management


๐Ÿ› ๏ธ How It Works

The Mediator Pattern includes:

  1. A Mediator Interface that defines communication methods

  2. A ConcreteMediator that implements coordination logic

  3. Colleague Classes that interact via the mediator

C# Example: Chat Room Mediator

// Mediator interface
public interface IChatRoomMediator
{
    void SendMessage(string message, User sender);
}

// Concrete Mediator
public class ChatRoom : IChatRoomMediator
{
    private List<User> _users = new List<User>();

    public void Register(User user)
    {
        _users.Add(user);
        user.SetChatRoom(this);
    }

    public void SendMessage(string message, User sender)
    {
        foreach (var user in _users)
        {
            if (user != sender)
            {
                user.Receive(message, sender.Name);
            }
        }
    }
}

// Colleague
public class User
{
    public string Name { get; }
    private IChatRoomMediator _chatRoom;

    public User(string name)
    {
        Name = name;
    }

    public void SetChatRoom(IChatRoomMediator chatRoom)
    {
        _chatRoom = chatRoom;
    }

    public void Send(string message)
    {
        _chatRoom.SendMessage(message, this);
    }

    public void Receive(string message, string sender)
    {
        Console.WriteLine($"{Name} received from {sender}: {message}");
    }
}

Usage

var chatRoom = new ChatRoom();
var alice = new User("Alice");
var bob = new User("Bob");
var charlie = new User("Charlie");

chatRoom.Register(alice);
chatRoom.Register(bob);
chatRoom.Register(charlie);

alice.Send("Hello, everyone!");

โš–๏ธ Pros and Cons

โœ… Pros

  • Centralizes control over communication logic

  • Promotes loose coupling between components

  • Makes components easier to reuse and maintain

  • Simplifies complex interaction networks

โŒ Cons

  • Mediator can become a monolithic god object if overused

  • Increases the number of classes

  • May require careful planning to keep the mediator lightweight


๐Ÿงช Testing Benefits

  • You can test the mediator independently from the colleagues

  • Each component can be tested in isolation

  • Easy to mock or replace the mediator for unit tests

  • Test communication rules in a single place


๐ŸŒ Real-World Use Cases

  • UI Frameworks: Dialogs managing buttons, input fields and menus

  • Air Traffic Control Systems: All planes report to a central controller, not to each other

  • Chat Applications: Central chatroom or server distributes messages to users

  • Workflow Engines: Steps of a process communicate through a central coordinator


๐Ÿ”— Related Patterns

  • Observer: Allows broadcast-style communication to multiple listeners but without coordination

  • Command: Encapsulates requests but does not handle interaction between components

  • Facade: Simplifies access to a complex subsystem but does not coordinate interaction like a mediator


๐Ÿ“Œ Final Thoughts

The Mediator Pattern is your go-to solution when your system is turning into a spaghetti mess of interconnected objects. It provides a clean way to control communication, reduce dependencies and promote maintainability. When used properly, the mediator becomes the hub that keeps your components both simple and powerful.