Skip to content Skip to footer

Mediator Design Pattern: Reducing Object Dependencies

You’re building a dialog box with multiple UI components: buttons, text fields, checkboxes. When one component changes, others need to update. Without careful design, every component would need references to every other component, creating a web of dependencies. The Mediator pattern centralizes communication, reducing chaotic dependencies between objects.

Problem

Consider a chat application where users can send messages:

public class User
{
    public string Name { get; set; }

    public void SendMessage(string message, User recipient)
    {
        Console.WriteLine($"{Name} to {recipient.Name}: {message}");
        recipient.ReceiveMessage(message, this);
    }

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

This works for two users, but what about group chats? Each user would need references to all other users:

public class User
{
    public string Name { get; set; }
    public List<User> Contacts { get; set; } = new();

    public void SendToAll(string message)
    {
        foreach (var contact in Contacts)
        {
            contact.ReceiveMessage(message, this);
        }
    }
}

This creates tight coupling. Adding features like message history, user status, or rooms becomes difficult.

Solution

The Mediator pattern defines an object that encapsulates how a set of objects interact. It promotes loose coupling by keeping objects from referring to each other explicitly.

Definition: The Mediator pattern lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.

Instead of objects communicating directly, they communicate through a mediator, which coordinates the interaction.

Pseudocode

Colleague1, Colleague2, Colleague3
    ↓ all communicate through
Mediator (IChatMediator)
    ↓ coordinates
Communication between colleagues

Examples

Here’s a complete, runnable C# implementation:

using System;
using System.Collections.Generic;

// Mediator interface
public interface IChatMediator
{
    void SendMessage(string message, User user);
    void AddUser(User user);
}

// Concrete mediator
public class ChatMediator : IChatMediator
{
    private List<User> _users = new List<User>();

    public void AddUser(User user)
    {
        _users.Add(user);
    }

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

// Colleague
public class User
{
    private IChatMediator _mediator;
    public string Name { get; set; }

    public User(string name, IChatMediator mediator)
    {
        Name = name;
        _mediator = mediator;
    }

    public void Send(string message)
    {
        Console.WriteLine($"{Name} sends: {message}");
        _mediator.SendMessage(message, this);
    }

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

// Usage
class Program
{
    static void Main()
    {
        var mediator = new ChatMediator();

        var alice = new User("Alice", mediator);
        var bob = new User("Bob", mediator);
        var charlie = new User("Charlie", mediator);

        mediator.AddUser(alice);
        mediator.AddUser(bob);
        mediator.AddUser(charlie);

        alice.Send("Hello everyone!");
        bob.Send("Hi Alice!");
    }
}

Output:

Alice sends: Hello everyone!
Bob received from Alice: Hello everyone!
Charlie received from Alice: Hello everyone!
Bob sends: Hi Alice!
Alice received from Bob: Hi Alice!
Charlie received from Bob: Hi Alice!

Real-World Example: Air Traffic Control

// Mediator
public interface IAirTrafficControl
{
    void RegisterAircraft(Aircraft aircraft);
    void RequestLanding(Aircraft aircraft);
    void NotifyLanding(Aircraft aircraft);
}

public class AirTrafficControl : IAirTrafficControl
{
    private List<Aircraft> _aircrafts = new List<Aircraft>();
    private Aircraft _landingAircraft = null;

    public void RegisterAircraft(Aircraft aircraft)
    {
        _aircrafts.Add(aircraft);
    }

    public void RequestLanding(Aircraft aircraft)
    {
        if (_landingAircraft == null)
        {
            _landingAircraft = aircraft;
            Console.WriteLine($"{aircraft.Name} cleared for landing");
        }
        else
        {
            Console.WriteLine($"{aircraft.Name} must wait. {_landingAircraft.Name} is landing");
        }
    }

    public void NotifyLanding(Aircraft aircraft)
    {
        if (_landingAircraft == aircraft)
        {
            _landingAircraft = null;
            Console.WriteLine($"{aircraft.Name} has landed");
        }
    }
}

// Colleague
public class Aircraft
{
    private IAirTrafficControl _atc;
    public string Name { get; set; }

    public Aircraft(string name, IAirTrafficControl atc)
    {
        Name = name;
        _atc = atc;
        _atc.RegisterAircraft(this);
    }

    public void RequestLanding()
    {
        _atc.RequestLanding(this);
    }

    public void Land()
    {
        _atc.NotifyLanding(this);
    }
}

// Usage
var atc = new AirTrafficControl();
var flight1 = new Aircraft("Flight 101", atc);
var flight2 = new Aircraft("Flight 202", atc);

flight1.RequestLanding();
flight2.RequestLanding();
flight1.Land();
flight2.RequestLanding();

UI Component Example

// Mediator
public interface IDialogMediator
{
    void Notify(Component sender, string eventName);
}

public class LoginDialogMediator : IDialogMediator
{
    public TextBox UsernameBox { get; set; }
    public TextBox PasswordBox { get; set; }
    public CheckBox RememberCheckbox { get; set; }
    public Button LoginButton { get; set; }

    public void Notify(Component sender, string eventName)
    {
        if (sender == UsernameBox && eventName == "TextChanged")
        {
            ValidateForm();
        }
        else if (sender == PasswordBox && eventName == "TextChanged")
        {
            ValidateForm();
        }
        else if (sender == LoginButton && eventName == "Click")
        {
            AttemptLogin();
        }
    }

    private void ValidateForm()
    {
        bool isValid = !string.IsNullOrEmpty(UsernameBox.Text) &&
                      !string.IsNullOrEmpty(PasswordBox.Text);
        LoginButton.Enabled = isValid;
    }

    private void AttemptLogin()
    {
        Console.WriteLine($"Logging in: {UsernameBox.Text}");
    }
}

// Component base
public abstract class Component
{
    protected IDialogMediator _mediator;

    public Component(IDialogMediator mediator)
    {
        _mediator = mediator;
    }
}

public class TextBox : Component
{
    private string _text = "";
    public string Text
    {
        get => _text;
        set
        {
            _text = value;
            _mediator?.Notify(this, "TextChanged");
        }
    }

    public TextBox(IDialogMediator mediator) : base(mediator) { }
}

public class Button : Component
{
    private bool _enabled = false;
    public bool Enabled
    {
        get => _enabled;
        set
        {
            _enabled = value;
            Console.WriteLine($"Button enabled: {value}");
        }
    }

    public Button(IDialogMediator mediator) : base(mediator) { }

    public void Click()
    {
        if (_enabled)
        {
            _mediator?.Notify(this, "Click");
        }
    }
}

public class CheckBox : Component
{
    public bool Checked { get; set; }

    public CheckBox(IDialogMediator mediator) : base(mediator) { }
}

Applications

Enterprise Use Cases:

  1. UI Frameworks: Coordinating interactions between UI components
  2. Chat Applications: Managing message routing between users
  3. Workflow Systems: Coordinating steps in business processes
  4. Event Systems: Centralizing event handling
  5. Game Development: Managing interactions between game objects
  6. Trading Systems: Coordinating buy/sell orders
  7. Air Traffic Control: Managing aircraft communications
  8. Form Validation: Coordinating validation between form fields

Benefits:

  • Reduces coupling between colleagues
  • Centralizes control logic
  • Makes it easier to understand how objects interact
  • Simplifies maintenance
  • Follows Single Responsibility Principle—mediator handles coordination

When to Use:

  • A set of objects communicate in well-defined but complex ways
  • Reusing objects is difficult because they refer to many other objects
  • Behavior distributed between classes should be customizable without subclassing
  • You want to centralize communication logic

Mediator vs Observer:

  • Mediator: Colleagues communicate through mediator (many-to-many)
  • Observer: Subject notifies observers directly (one-to-many)

Key Insight: The Mediator pattern centralizes communication between objects. Instead of objects communicating directly (which creates tight coupling), they communicate through a mediator. This reduces dependencies and makes the system easier to understand and maintain.

Leave a Comment