✈️ El Patrón Mediator: La Torre de Control
El Patrón de diseño Mediator es un patrón de comportamiento que se encarga de reducir las dependencias caóticas entre objetos. En lugar de que todos hablen con todos, todos hablan con un punto central.
Cuando tienes muchos objetos interactuando, las conexiones se multiplican exponencialmente. Si A conoce a B, C y D; y B conoce a A y C… acabas con un plato de espaguetis imposible de mantener. Cada vez que tocas un componente, se rompen tres más.
Dicho de otra forma, creas un objeto Mediator que se encarga de coordinar cómo interactúan los demás. Los objetos ya no se hablan entre sí, solo hablan con el mediator, y el mediator decide qué hacer.

🗼 1. La Torre de Control
Imagina un aeropuerto con 20 aviones en el aire (los Colegas/Componentes):
- Si cada piloto tuviera que llamar a los otros 19 para ver si puede aterrizar, sería un caos total. “Oye Paquito 747, ¿estás en pista?”. “No, soy Pepe Airbus 320, estoy despegando”. Toda esta vaina loca multiplicada por 20.
- En su lugar, el piloto llama a la Torre de Control (el Mediador).
- “Torre, vuelo IB-123 solicitando aterrizaje”. La Torre mira su radar, ve que la pista está libre y dice “concedido”. O ve que hay otro despegando y dice “espere en holding”.
El piloto no sabe ni le importa qué otros aviones hay, solo obedece a la Torre. Y si mañana el aeropuerto pasa de 20 a 50 aviones, el piloto no tiene que cambiar nada. La Torre se encarga.
Sin mediador cada avión conectado con todos los demás = n × (n-1) conexiones. Con mediador: cada avión conectado solo con la torre = n conexiones, la diferencia es bastante importante.
¿Qué tipo de patrón de diseño es Mediator?
🛠️ 2. Los roles del patrón
Declara el método para que los componentes se comuniquen. Normalmente un notificar(emisor, evento). Es el contrato que todo mediador cumple.
Mantiene referencias a todos los componentes y contiene la lógica de coordinación. Sabe quién tiene que hablar con quién y cuándo.
Cada componente guarda una referencia al mediador. No se conocen entre sí. Cuando algo pasa, avisan al mediador y este decide qué hacer.
Los componentes son ignorantes unos de otros, solo conocen al mediador. Esto significa que puedes reutilizar un componente en otro contexto sin arrastrar dependencias.
¿Cuál es el principal problema que resuelve el patrón Mediator?
🧑💻 3. Ejemplo práctico en PHP con un Chat Room
Vamos con el clásico ejemplo de una sala de chat donde los usuarios se comunican. Sin mediator, cada usuario necesitaría referencias a todos los demás. Con mediator, solo conocen la sala.
<?php
// 1. Interfaz Mediador
interface ChatMediator
{
public function enviarMensaje(string $mensaje, Usuario $emisor): void;
public function enviarPrivado(string $mensaje, Usuario $emisor, string $destinatario): void;
public function agregarUsuario(Usuario $usuario): void;
}
// 2. Mediador Concreto
class SalaChat implements ChatMediator
{
/** @var Usuario[] */
private array $usuarios = [];
public function agregarUsuario(Usuario $usuario): void
{
$this->usuarios[] = $usuario;
// Notificamos a los demás que alguien entró
foreach ($this->usuarios as $u) {
if ($u !== $usuario) {
$u->recibir("📢 {$usuario->getNombre()} se ha unido al chat");
}
}
}
public function enviarMensaje(string $mensaje, Usuario $emisor): void
{
foreach ($this->usuarios as $usuario) {
if ($usuario !== $emisor) {
$usuario->recibir("[{$emisor->getNombre()}]: $mensaje");
}
}
}
public function enviarPrivado(string $mensaje, Usuario $emisor, string $destinatario): void
{
foreach ($this->usuarios as $usuario) {
if ($usuario->getNombre() === $destinatario) {
$usuario->recibir("[Privado de {$emisor->getNombre()}]: $mensaje");
return;
}
}
$emisor->recibir("⚠️ Usuario '$destinatario' no encontrado");
}
}
// 3. Componente (Colega)
class Usuario
{
private ChatMediator $mediator;
private string $nombre;
/** @var string[] */
private array $bandeja = [];
public function __construct(ChatMediator $mediator, string $nombre)
{
$this->mediator = $mediator;
$this->nombre = $nombre;
}
public function getNombre(): string
{
return $this->nombre;
}
public function enviar(string $mensaje): void
{
echo "📤 {$this->nombre} envía: $mensaje\n";
$this->mediator->enviarMensaje($mensaje, $this);
}
public function enviarPrivado(string $mensaje, string $destinatario): void
{
echo "📤 {$this->nombre} → $destinatario (privado): $mensaje\n";
$this->mediator->enviarPrivado($mensaje, $this, $destinatario);
}
public function recibir(string $mensaje): void
{
$this->bandeja[] = $mensaje;
echo " 📥 {$this->nombre} recibe: $mensaje\n";
}
public function getBandeja(): array
{
return $this->bandeja;
}
}
// 4. Uso
$sala = new SalaChat();
$juan = new Usuario($sala, "Juan");
$maria = new Usuario($sala, "María");
$pepe = new Usuario($sala, "Pepe");
$sala->agregarUsuario($juan);
$sala->agregarUsuario($maria);
$sala->agregarUsuario($pepe);
echo "\n--- Mensaje público ---\n";
$juan->enviar("¡Hola a todos!");
echo "\n--- Mensaje privado ---\n";
$maria->enviarPrivado("Oye, ¿quedamos luego?", "Juan");
// Salida:
// 📢 María se ha unido al chat (lo recibe Juan)
// 📢 Pepe se ha unido al chat (lo reciben Juan y María)
//
// --- Mensaje público ---
// 📤 Juan envía: ¡Hola a todos!
// 📥 María recibe: [Juan]: ¡Hola a todos!
// 📥 Pepe recibe: [Juan]: ¡Hola a todos!
//
// --- Mensaje privado ---
// 📤 María → Juan (privado): Oye, ¿quedamos luego?
// 📥 Juan recibe: [Privado de María]: Oye, ¿quedamos luego?
Juan no tiene ni idea de que María y Pepe existen. Solo conoce a $sala. Si mañana entran 50 usuarios más, la clase Usuario no cambia ni una línea. Toda la coordinación está en SalaChat.
¿Cuántas conexiones necesitas con Mediator si tienes 10 componentes, frente a sin Mediator?
🖥️ 4. Ejemplo con un Formulario con componentes coordinados
Donde el Mediator brilla de verdad es en formularios y UI. Imagina un diálogo de compra con un campo de texto, un checkbox, un select y un botón. El checkbox “¿Envío express?” activa el select de franja horaria. El campo “código descuento” habilita el botón de “Aplicar”. Sin mediator, cada componente necesita conocer a los demás. Con mediator, solo avisan al DialogoMediator:
<?php
// Interfaz Mediador
interface DialogoMediator
{
public function notificar(Componente $emisor, string $evento): void;
}
// Componente base
abstract class Componente
{
protected ?DialogoMediator $mediator = null;
public function setMediator(DialogoMediator $mediator): void
{
$this->mediator = $mediator;
}
}
// Componentes concretos
class Checkbox extends Componente
{
private bool $marcado = false;
public function toggle(): void
{
$this->marcado = !$this->marcado;
echo ($this->marcado ? "☑" : "☐") . " Envío express\n";
$this->mediator?->notificar($this, 'toggle');
}
public function estaMarcado(): bool
{
return $this->marcado;
}
}
class SelectFranja extends Componente
{
private bool $habilitado = false;
public function habilitar(): void
{
$this->habilitado = true;
echo "🟢 Select de franja horaria: HABILITADO\n";
}
public function deshabilitar(): void
{
$this->habilitado = false;
echo "🔴 Select de franja horaria: DESHABILITADO\n";
}
}
class InputDescuento extends Componente
{
private string $valor = '';
public function escribir(string $texto): void
{
$this->valor = $texto;
echo "✏️ Código descuento: '$texto'\n";
$this->mediator?->notificar($this, 'input');
}
public function getValor(): string
{
return $this->valor;
}
}
class BotonAplicar extends Componente
{
public function habilitar(): void
{
echo "🟢 Botón 'Aplicar descuento': HABILITADO\n";
}
public function deshabilitar(): void
{
echo "🔴 Botón 'Aplicar descuento': DESHABILITADO\n";
}
}
// Mediador Concreto: coordina todo el formulario
class FormularioCompra implements DialogoMediator
{
public function __construct(
private Checkbox $checkbox,
private SelectFranja $select,
private InputDescuento $input,
private BotonAplicar $boton
) {
$this->checkbox->setMediator($this);
$this->select->setMediator($this);
$this->input->setMediator($this);
$this->boton->setMediator($this);
}
public function notificar(Componente $emisor, string $evento): void
{
if ($emisor instanceof Checkbox && $evento === 'toggle') {
// Si marcan envío express → habilitar select de franja
if ($this->checkbox->estaMarcado()) {
$this->select->habilitar();
} else {
$this->select->deshabilitar();
}
}
if ($emisor instanceof InputDescuento && $evento === 'input') {
// Si hay código descuento → habilitar botón aplicar
if (strlen($this->input->getValor()) > 0) {
$this->boton->habilitar();
} else {
$this->boton->deshabilitar();
}
}
}
}
// Uso
$checkbox = new Checkbox();
$select = new SelectFranja();
$input = new InputDescuento();
$boton = new BotonAplicar();
$formulario = new FormularioCompra($checkbox, $select, $input, $boton);
echo "--- El usuario marca envío express ---\n";
$checkbox->toggle();
echo "\n--- El usuario escribe un código descuento ---\n";
$input->escribir("VERANO20");
echo "\n--- El usuario desmarca envío express ---\n";
$checkbox->toggle();
// Salida:
// --- El usuario marca envío express ---
// ☑ Envío express
// 🟢 Select de franja horaria: HABILITADO
//
// --- El usuario escribe un código descuento ---
// ✏️ Código descuento: 'VERANO20'
// 🟢 Botón 'Aplicar descuento': HABILITADO
//
// --- El usuario desmarca envío express ---
// ☐ Envío express
// 🔴 Select de franja horaria: DESHABILITADO
Ningún componente conoce a los demás. El Checkbox no sabe que existe un SelectFranja. Solo dice “oye mediador, me
han toggleado” y el mediador decide qué hacer. Si mañana añades un LabelPrecioExpress que aparece al marcar el
checkbox, solo tocas el mediador. Ningún componente existente cambia.
En el ejemplo del formulario, ¿por qué el Checkbox no conoce directamente al SelectFranja?
🌍 5. Casos de uso en el mundo real
En frameworks como Laravel o Symfony, el Controller actúa como mediator entre la Request, los Services, los Repositories y la Response. Ninguno se conoce entre sí.
RabbitMQ, Kafka, Redis Pub/Sub... el broker es un mediador que recibe mensajes de productores y los enruta a consumidores. Nadie se conoce directamente.
Un formulario complejo donde botones, inputs, selects y checkboxes interactúan. El DialogMediator coordina quién se habilita, quién se oculta y quién valida.
El servidor de chat o el game server es el mediador. Los jugadores/usuarios solo hablan con el servidor, que se encarga de retransmitir, validar y sincronizar.
🔄 6. Comparativa con otros patrones
| Patrón | Propósito | Diferencia clave |
|---|---|---|
| Mediator | Centralizar la comunicación entre componentes | El mediador coordina interacciones complejas entre múltiples objetos |
| Observer | Notificar a suscriptores cuando algo cambia | Observer es unidireccional (sujeto → observadores). Mediator es bidireccional (componentes ↔ mediador) |
| Facade | Simplificar una interfaz compleja | Facade es unidireccional: el cliente usa la fachada. Los subsistemas no conocen la fachada. En Mediator la comunicación va en ambas direcciones |
| Command | Encapsular una acción como objeto | Un Mediator puede usar Commands internamente para despachar las acciones entre componentes |
| Chain of Responsibility | Pasar una petición por una cadena de handlers | CoR es secuencial: la petición va de handler en handler. Mediator centraliza: todos hablan con un punto |
La confusión más común es Mediator vs Observer. La diferencia es que en Observer, el sujeto no sabe qué hacen los observadores, solo les avisa. En Mediator, el mediador conoce y coordina activamente las interacciones. Un Observer puede ser parte de un Mediator, pero no al revés.
¿Cuál es la principal diferencia entre Mediator y Observer?
⚖️ 7. Relación con SOLID
Los componentes solo se preocupan de su propia lógica. La coordinación entre ellos es responsabilidad exclusiva del mediador. Cada clase tiene un solo motivo para cambiar.
¿Nuevo componente? Lo creas, lo registras en el mediador y listo. Los componentes existentes no cambian. Solo el mediador necesita saber del nuevo.
Los componentes dependen de la abstracción Mediator, no del mediador concreto. Puedes cambiar SalaChat por SalaPrivada sin tocar Usuario.
Cualquier mediador que implemente la interfaz puede sustituir a otro. Un SalaChatConHistorial puede reemplazar a SalaChat sin romper nada.
¿Qué principio SOLID se cumple al mover la lógica de coordinación fuera de los componentes?
✅ 8. Ventajas y desventajas
- Desacoplamiento: Los componentes no se conocen entre sí. Puedes modificar uno sin romper otros.
- Centralización: Toda la lógica de interacción está en un sitio. Fácil de entender y debuggear.
- Reutilización: Los componentes son independientes. Puedes usar
Checkboxen otro formulario con otro mediador. - SRP: La coordinación no contamina la lógica de cada componente.
- Escalable: Añadir un componente nuevo solo requiere actualizar el mediador.
- God Object: El mediador puede crecer hasta convertirse en una clase monstruo que lo sabe y lo hace todo.
- Punto único de fallo: Si el mediador falla, todo el sistema de comunicación se cae.
- Indirección: Seguir el flujo de comunicación es menos directo. Hay que pasar siempre por el mediador.
- Complejidad innecesaria: Si solo tienes 2-3 componentes con interacciones simples, el mediador sobra.
⚠️ 9. Errores comunes
1. El God Object: meter toda la lógica en el mediador
El error número uno. El mediador debería coordinar, no ejecutar. Si tu mediador tiene 500 líneas con lógica de negocio, es Godzilla:
// ❌ MAL: el mediador hace de todo
class SalaChat implements ChatMediator
{
public function enviarMensaje(string $mensaje, Usuario $emisor): void
{
// Valida el mensaje
$mensaje = $this->filtrarPalabrasProhibidas($mensaje);
// Lo guarda en BD
$this->guardarEnHistorial($mensaje, $emisor);
// Lo envía por WebSocket
$this->enviarPorWebSocket($mensaje);
// Lo envía por email a los desconectados
$this->notificarPorEmail($mensaje);
// Actualiza estadísticas
$this->actualizarContadorMensajes($emisor);
// ...200 líneas más
}
}
// ✅ BIEN: el mediador solo coordina, delega la lógica
class SalaChat implements ChatMediator
{
public function __construct(
private FiltroMensajes $filtro,
private Historial $historial,
private Notificador $notificador
) {}
public function enviarMensaje(string $mensaje, Usuario $emisor): void
{
$mensajeLimpio = $this->filtro->filtrar($mensaje);
$this->historial->guardar($mensajeLimpio, $emisor);
foreach ($this->usuarios as $usuario) {
if ($usuario !== $emisor) {
$this->notificador->enviar($mensajeLimpio, $usuario);
}
}
}
}
2. Componentes que se saltan al mediador
Si un componente accede directamente a otro, estás rompiendo todo el propósito del patrón:
// ❌ MAL: el componente conoce a otro componente
class Checkbox extends Componente
{
private SelectFranja $select; // ← Dependencia directa
public function toggle(): void
{
$this->marcado = !$this->marcado;
$this->select->habilitar(); // ← Se salta al mediador
}
}
// ✅ BIEN: avisa al mediador y él decide
class Checkbox extends Componente
{
public function toggle(): void
{
$this->marcado = !$this->marcado;
$this->mediator?->notificar($this, 'toggle');
}
}
3. Crear un mediador para dos componentes
Si solo tienes dos objetos que interactúan, un mediador es matar moscas a cañonazos. El patrón tiene sentido cuando hay muchas relaciones cruzadas:
// ❌ Sobreingeniería: mediador para dos clases
class MediadorAB implements Mediator
{
public function __construct(
private ComponenteA $a,
private ComponenteB $b
) {}
public function notificar(Componente $emisor, string $evento): void
{
if ($emisor === $this->a) {
$this->b->hacerAlgo();
}
}
}
// ✅ Más simple: que A llame a B directamente
$a->setCallback(fn() => $b->hacerAlgo());
4. Mediador que conoce implementaciones concretas
Si el mediador depende de clases concretas en vez de interfaces, pierdes la capacidad de intercambiar componentes:
// ❌ MAL: acoplado a clases concretas
class FormularioCompra implements DialogoMediator
{
private CheckboxEnvioExpress $checkbox; // ← Concreto
private SelectFranjaHoraria $select; // ← Concreto
}
// ✅ BIEN: depende de abstracciones
class FormularioCompra implements DialogoMediator
{
private Togglable $checkbox; // ← Interfaz
private Selectable $select; // ← Interfaz
}
¿Qué riesgo tiene un Mediator mal diseñado?
🤔 10. ¿Cuándo usarlo y cuándo no?
- Tienes muchos componentes con dependencias cruzadas tipo "muchos a muchos"
- El diagrama de dependencias parece una tela de araña
- Quieres que los componentes sean reutilizables en otros contextos
- Necesitas centralizar reglas de interacción complejas (formularios, diálogos, flujos de trabajo)
- Trabajas con UI compleja donde inputs, botones y selects se afectan mutuamente
- Solo tienes 2-3 componentes con interacciones simples
- La comunicación es unidireccional (ahí un Observer basta)
- El mediador se está convirtiendo en un God Object (señal de que necesitas dividirlo)
- Los componentes ya están naturalmente desacoplados y la interacción es trivial
Si añadir un componente nuevo te obliga a modificar 5 clases existentes, necesitas un Mediator. Si solo tocas una, probablemente no.
¿Cuándo NO deberías usar el patrón Mediator?
💡 11. Conclusión
El Patrón Mediator es el antídoto contra el acoplamiento entre componentes. Cuando tus objetos se conocen demasiado y cada cambio provoca una cascada de modificaciones, es hora de meter un mediador en medio.
Su idea es simple: los componentes no se hablan entre sí, hablan con el mediador, y el mediador decide. Esto centraliza la lógica de coordinación en un solo punto, hace que los componentes sean reutilizables y facilita mucho el testing (puedes testear cada componente por separado con un mediador mock).
Eso sí, cuidado con pasarte y marcarte un God Object. Si tu mediador empieza a tener 1000 líneas, es señal de que necesitas dividirlo en mediadores más pequeños o que estás metiendo lógica de negocio donde no debe ir.
EA, ¡saluditos y nos vemos en los bares! 🍻