🚨 ¡Nueva review! ¡Mi teclado ideal! ⌨️ Perfecto para programar, el Logitech MX Keys S . ¡Échale un ojo! 👀

El Patrón de diseño Mediator

El controlador aéreo que evita que tus objetos se estrellen.

Escrito por domin el 27 de noviembre de 2025 · Actualizado el 8 de febrero de 2026

✈️ 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.

Diagrama del Patrón Mediator.

🗼 1. La Torre de Control

Imagina un aeropuerto con 20 aviones en el aire (los Colegas/Componentes):

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

Interfaz Mediador

Declara el método para que los componentes se comuniquen. Normalmente un notificar(emisor, evento). Es el contrato que todo mediador cumple.

Mediador Concreto

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.

Componentes (Colegas)

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

Controladores MVC

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í.

Event Bus / Message Broker

RabbitMQ, Kafka, Redis Pub/Sub... el broker es un mediador que recibe mensajes de productores y los enruta a consumidores. Nadie se conoce directamente.

Diálogos de UI

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.

Salas de chat / Multiplayer

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ónPropósitoDiferencia clave
MediatorCentralizar la comunicación entre componentesEl mediador coordina interacciones complejas entre múltiples objetos
ObserverNotificar a suscriptores cuando algo cambiaObserver es unidireccional (sujeto → observadores). Mediator es bidireccional (componentes ↔ mediador)
FacadeSimplificar una interfaz complejaFacade es unidireccional: el cliente usa la fachada. Los subsistemas no conocen la fachada. En Mediator la comunicación va en ambas direcciones
CommandEncapsular una acción como objetoUn Mediator puede usar Commands internamente para despachar las acciones entre componentes
Chain of ResponsibilityPasar una petición por una cadena de handlersCoR 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

SRP (Single Responsibility)

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.

OCP (Open/Closed)

¿Nuevo componente? Lo creas, lo registras en el mediador y listo. Los componentes existentes no cambian. Solo el mediador necesita saber del nuevo.

DIP (Dependency Inversion)

Los componentes dependen de la abstracción Mediator, no del mediador concreto. Puedes cambiar SalaChat por SalaPrivada sin tocar Usuario.

LSP (Liskov Substitution)

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

Ventajas
  • 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 Checkbox en 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.
Desventajas
  • 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?

Úsalo cuando...
  • 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
No lo uses cuando...
  • 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! 🍻