patron-mediator

Mediator Pattern: coordinating communication

  • 6 min

The Mediator is an object that coordinates communication between several components so they don’t depend directly on each other.

Imagine the air traffic of an international airport. Hundreds of planes take off and land.

If every pilot had to talk to every other pilot to coordinate (“Hey Boeing 747, can you let me through?”, “Wait Airbus, I’m going first”), it would be absolute chaos. The system would collapse.

To solve this, there is the Control Tower.

  • Pilots do not talk to each other.
  • Pilots talk only to the Tower.
  • The Tower has the global view and decides who lands and who waits.

When many objects interact with each other, we can end up with a “everyone against everyone” network of dependencies. Mediator concentrates the coordination rules that belong to that interaction, without forcing every operation of the application to pass through a single class.

The Problem: The Mesh of Dependencies

Suppose we are designing a complex Registration Form in a Graphical User Interface (GUI). We have:

  1. A TextBox for the name.
  2. A CheckBox for “I accept the terms”.
  3. A Button for “Register”.

The logic is:

  • If the TextBox is empty, the Button is disabled.
  • If the CheckBox is unchecked, the Button is disabled.
  • If you check the CheckBox, the TextBox must be validated.

If we program this “the brute force way”, the CheckBox will have to know about the Button to enable it. The TextBox will have to know about the Button.

This generates High Coupling.

  • You cannot reuse that Button in another window, because it has code inside that checks for specific CheckBoxes.
  • If you add a new field, you have to modify all the classes.

The Solution: Centralize the Logic

The Mediator pattern converts that “many-to-many” relationship into a “one-to-many” relationship (Star Topology).

The components (buttons, text boxes) are called Colleagues.

  • Colleagues KNOW NOTHING about other colleagues.
  • They only know the Mediator.
  • When something happens (click, text change), they notify the Mediator: “Hey, I was clicked”.
  • The Mediator receives the notification and decides what to do (enable the button, show an error, etc.).

Implementation in C#

Let’s build that decoupled registration form.

The Mediator Interface

public interface IMediator
{
    // The generic method to receive notifications
    // sender: Who is sending it
    // evento: What happened ("click", "keypress", etc.)
    void Notificar(object sender, string evento);
}
Copied!

The Components (Colleagues)

Notice that these components have no business logic. They are purely visual and reusable. A Button here works for a registration, a login, or a calculator.

// Base class for all UI elements
public abstract class Componente
{
    protected IMediator _mediator;

    public Componente(IMediator mediator = null)
    {
        _mediator = mediator;
    }

    public void SetMediator(IMediator mediator)
    {
        _mediator = mediator;
    }
}

// Component 1: Button
public class Boton : Componente
{
    public bool Habilitado { get; set; } = false;

    public void Click()
    {
        if (Habilitado)
             _mediator.Notificar(this, "click");
        else
             Console.WriteLine("⛔ The button is disabled.");
    }
}

// Component 2: TextBox
public class TextBox : Componente
{
    public string Texto { get; set; } = "";

    public void Escribir(string texto)
    {
        Texto = texto;
        // Notify the mediator that something changed
        _mediator.Notificar(this, "input");
    }
}

// Component 3: CheckBox
public class CheckBox : Componente
{
    public bool Marcado { get; set; } = false;

    public void Marcar()
    {
        Marcado = !Marcado;
        _mediator.Notificar(this, "check");
    }
}
Copied!

The Concrete Mediator (The Brain)

This is where all the interaction logic resides. If we want to change the form rules, we only touch this class.

public class DialogoRegistro : IMediator
{
    // The mediator knows the concrete components
    private TextBox _usuario;
    private CheckBox _terminos;
    private Boton _botonRegistro;

    // Method to register the components with the mediator
    public void RegistrarComponentes(TextBox usuario, CheckBox terminos, Boton boton)
    {
        _usuario = usuario;
        _usuario.SetMediator(this);

        _terminos = terminos;
        _terminos.SetMediator(this);

        _botonRegistro = boton;
        _botonRegistro.SetMediator(this);
    }

    // THE BRAIN OF THE OPERATION
    public void Notificar(object sender, string evento)
    {
        if (sender == _usuario && evento == "input")
        {
            Console.WriteLine($"Mediator: User typed '{_usuario.Texto}'");
            ValidarFormulario();
        }
        
        if (sender == _terminos && evento == "check")
        {
            Console.WriteLine($"Mediator: Terms changed to {_terminos.Marcado}");
            ValidarFormulario();
        }

        if (sender == _botonRegistro && evento == "click")
        {
            Console.WriteLine("Mediator: Registering user in database!");
            Console.WriteLine($" -> User: {_usuario.Texto}");
        }
    }

    private void ValidarFormulario()
    {
        // Rule: Button enabled ONLY if there is text AND terms are accepted
        bool esValido = !string.IsNullOrEmpty(_usuario.Texto) && _terminos.Marcado;
        
        if (esValido)
        {
            Console.WriteLine("Mediator: Valid form. Enabling button.");
            _botonRegistro.Habilitado = true;
        }
        else
        {
            Console.WriteLine("Mediator: Invalid form. Disabling button.");
            _botonRegistro.Habilitado = false;
        }
    }
}
Copied!

The Client

class Program
{
    static void Main(string[] args)
    {
        // 1. Create the individual components
        var txtUsuario = new TextBox();
        var chkTerminos = new CheckBox();
        var btnEnviar = new Boton();

        // 2. Create the mediator and connect everything
        var dialogo = new DialogoRegistro();
        dialogo.RegistrarComponentes(txtUsuario, chkTerminos, btnEnviar);

        Console.WriteLine("--- User Interaction ---");
        
        // The user tries to click (shouldn't work)
        btnEnviar.Click(); 

        // The user types their name
        txtUsuario.Escribir("LuisLlamas");

        // The user accepts the terms
        chkTerminos.Marcar();

        // Now it should work
        btnEnviar.Click();
    }
}
Copied!

Output:

--- User Interaction ---
⛔ The button is disabled.

Mediator: User typed 'LuisLlamas'
Mediator: Invalid form. Disabling button.

Mediator: Terms changed to True
Mediator: Valid form. Enabling button.

Mediator: Registering user in database!
 -> User: LuisLlamas
Copied!

The Danger of the “God Object”

The biggest risk is that the mediator grows too large. If you put all the application logic in a single class, you will have a God Object that is hard to maintain.

To avoid this:

  • Create small, specific mediators for each screen or dialog.
  • Do not put complex business logic (calculations, database) in the mediator; the mediator should only coordinate the UI.

The Mediator pattern is essential in the development of graphical user interfaces (Windows Forms, WPF, and even in the state management of modern JS frameworks like Redux, which acts as a great state mediator). It helps us keep components clean and reusable.

Mediator vs Observer

This is the classic question. Both serve to communicate objects.

  • Observer: Used for “One-to-Many” communication. The Subject doesn’t know who is listening or what they will do with the info. It is more dynamic and decentralized.
  • Mediator: Used for “Many-to-Many” communication. The Mediator does know who the colleagues are and orchestrates the flow. It centralizes the logic.

They are often used together: Components can use the Observer pattern to notify the Mediator, and the Mediator executes the logic.