El Mediator es un objeto que coordina la comunicación entre varios componentes para que no dependan directamente unos de otros.
Imaginad el tráfico aéreo de un aeropuerto internacional. Cientos de aviones despegan y aterrizan.
Si cada piloto tuviera que hablar con todos los demás pilotos para coordinarse (“Oye Boeing 747, ¿me dejas pasar?”, “Espera Airbus, que voy yo”), sería un caos absoluto. El sistema colapsaría.
Para solucionar esto, existe la Torre de Control.
- Los pilotos no hablan entre ellos.
- Los pilotos hablan solo con la Torre.
- La Torre es quien tiene la visión global y decide quién aterriza y quién espera.
Cuando muchos objetos interactúan entre sí, podemos acabar con una red de dependencias «todos contra todos». Mediator concentra las reglas de coordinación que pertenecen a esa interacción, sin que eso obligue a pasar cualquier operación de la aplicación por una única clase.
El Problema: La Malla de Dependencias
Supongamos que estamos diseñando un Formulario de Registro complejo en una interfaz gráfica (UI). Tenemos:
- Un
TextBoxpara el nombre. - Un
CheckBoxde “Acepto términos”. - Un
Botonde “Registrar”.
La lógica es:
- Si el
TextBoxestá vacío, elBotonse deshabilita. - Si el
CheckBoxno está marcado, elBotonse deshabilita. - Si marcas el
CheckBox, hay que validar elTextBox.
Si programamos esto “a lo bruto”, el CheckBox tendrá que conocer al Botón para habilitarlo. El TextBox tendrá que conocer al Botón.
Esto genera un Acoplamiento Alto.
- No puedes reutilizar ese
Botonen otra ventana, porque tiene código dentro que comprueba CheckBoxes específicos. - Si añades un nuevo campo, tienes que modificar todas las clases.
La Solución: Centralizar la Lógica
El patrón Mediator convierte esa relación de “muchos a muchos” en una relación de “uno a muchos” (Topología de Estrella).
Los componentes (botones, cajas de texto) se llaman Colleagues (Colegas).
- Los colegas NO saben nada de los otros colegas.
- Solo conocen al Mediator.
- Cuando pasa algo (click, cambio de texto), avisan al Mediator: “Oye, me han hecho click”.
- El Mediator recibe el aviso y decide qué hacer (habilitar el botón, mostrar error, etc.).
Implementación en C#
Vamos a construir ese formulario de registro desacoplado.
La Interfaz del Mediador
public interface IMediator
{
// El método genérico para recibir notificaciones
// sender: Quién lo envía
// evento: Qué ha pasado ("click", "keypress", etc.)
void Notificar(object sender, string evento);
}Los Componentes (Colegas)
Fíjate que estos componentes no tienen lógica de negocio. Son puramente visuales y reutilizables. Un Boton aquí sirve para un registro, un login o una calculadora.
// Clase base para todos los elementos de UI
public abstract class Componente
{
protected IMediator _mediator;
public Componente(IMediator mediator = null)
{
_mediator = mediator;
}
public void SetMediator(IMediator mediator)
{
_mediator = mediator;
}
}
// Componente 1: Botón
public class Boton : Componente
{
public bool Habilitado { get; set; } = false;
public void Click()
{
if (Habilitado)
_mediator.Notificar(this, "click");
else
Console.WriteLine("⛔ El botón está deshabilitado.");
}
}
// Componente 2: TextBox
public class TextBox : Componente
{
public string Texto { get; set; } = "";
public void Escribir(string texto)
{
Texto = texto;
// Avisamos al mediador de que algo ha cambiado
_mediator.Notificar(this, "input");
}
}
// Componente 3: CheckBox
public class CheckBox : Componente
{
public bool Marcado { get; set; } = false;
public void Marcar()
{
Marcado = !Marcado;
_mediator.Notificar(this, "check");
}
}El Mediador Concreto (El Cerebro)
Aquí es donde reside toda la lógica de interacción. Si queremos cambiar las reglas del formulario, solo tocamos esta clase.
public class DialogoRegistro : IMediator
{
// El mediador conoce a los componentes concretos
private TextBox _usuario;
private CheckBox _terminos;
private Boton _botonRegistro;
// Método para registrar los componentes en el mediador
public void RegistrarComponentes(TextBox usuario, CheckBox terminos, Boton boton)
{
_usuario = usuario;
_usuario.SetMediator(this);
_terminos = terminos;
_terminos.SetMediator(this);
_botonRegistro = boton;
_botonRegistro.SetMediator(this);
}
// EL CEREBRO DE LA OPERACIÓN
public void Notificar(object sender, string evento)
{
if (sender == _usuario && evento == "input")
{
Console.WriteLine($"Mediador: Usuario ha escrito '{_usuario.Texto}'");
ValidarFormulario();
}
if (sender == _terminos && evento == "check")
{
Console.WriteLine($"Mediador: Términos cambiados a {_terminos.Marcado}");
ValidarFormulario();
}
if (sender == _botonRegistro && evento == "click")
{
Console.WriteLine("Mediador: ¡Registrando usuario en base de datos!");
Console.WriteLine($" -> Usuario: {_usuario.Texto}");
}
}
private void ValidarFormulario()
{
// Regla: Botón habilitado SOLO si hay texto Y términos aceptados
bool esValido = !string.IsNullOrEmpty(_usuario.Texto) && _terminos.Marcado;
if (esValido)
{
Console.WriteLine("Mediador: Formulario válido. Habilitando botón.");
_botonRegistro.Habilitado = true;
}
else
{
Console.WriteLine("Mediador: Formulario inválido. Deshabilitando botón.");
_botonRegistro.Habilitado = false;
}
}
}El Cliente
class Program
{
static void Main(string[] args)
{
// 1. Creamos los componentes sueltos
var txtUsuario = new TextBox();
var chkTerminos = new CheckBox();
var btnEnviar = new Boton();
// 2. Creamos el mediador y conectamos todo
var dialogo = new DialogoRegistro();
dialogo.RegistrarComponentes(txtUsuario, chkTerminos, btnEnviar);
Console.WriteLine("--- Interacción Usuario ---");
// El usuario intenta clicar (no debería funcionar)
btnEnviar.Click();
// El usuario escribe su nombre
txtUsuario.Escribir("LuisLlamas");
// El usuario acepta términos
chkTerminos.Marcar();
// Ahora sí debería funcionar
btnEnviar.Click();
}
}Salida:
--- Interacción Usuario ---
⛔ El botón está deshabilitado.
Mediador: Usuario ha escrito 'LuisLlamas'
Mediador: Formulario inválido. Deshabilitando botón.
Mediador: Términos cambiados a True
Mediador: Formulario válido. Habilitando botón.
Mediador: ¡Registrando usuario en base de datos!
-> Usuario: LuisLlamasEl peligro del “God Object”
El mayor riesgo es que el mediador crezca demasiado. Si metes toda la lógica de la aplicación en una sola clase, tendrás un God Object difícil de mantener.
Para evitarlo:
- Cread mediadores pequeños y específicos para cada pantalla o diálogo.
- No metáis lógica de negocio compleja (cálculos, BBDD) en el mediador; el mediador solo debe coordinar la UI.
El patrón Mediator es esencial en el desarrollo de interfaces gráficas (Windows Forms, WPF, e incluso en la gestión de estado de frameworks JS modernos como Redux, que actúa como un gran mediador de estado). Nos ayuda a mantener los componentes limpios y reutilizables.
Mediator vs Observer
Es la duda clásica. Ambos sirven para comunicar objetos.
- Observer: Se usa para comunicación “Uno a Muchos”. El Sujeto no sabe quién le escucha ni qué harán con la info. Es más dinámico y descentralizado.
- Mediator: Se usa para comunicación “Muchos a Muchos”. El Mediador sí sabe quiénes son los colegas y orquesta el flujo. Centraliza la lógica.
A menudo se usan juntos: Los componentes pueden usar el patrón Observer para notificar al Mediator, y el Mediator ejecuta la lógica.