25. Mediator (Mediador) - Patrón de Comportamiento

El patrón Mediator centraliza la comunicación entre objetos, evitando que los colegas se conozcan directamente. En lugar de referencias cruzadas y notificaciones en cascada, cada componente interactúa con un mediador que coordina las acciones y mantiene las dependencias bajo control.

Es frecuente en interfaces de usuario complejas, salas de chat, controladores de subsistemas y cualquier escenario donde múltiples objetos necesiten colaborar sin generar acoplamientos en red.

25.1 Problema y contexto

Cuando muchos objetos deben comunicarse entre sí, las referencias directas terminan generando grafos densos y difíciles de mantener. Un cambio en un componente puede afectar a varios otros debido a dependencias intrincadas. Además, las reglas de coordinación suelen dispersarse, complicando la extensión y las pruebas.

Mediator propone introducir un objeto central que encapsule la interacción: los colegas notifican al mediador y éste decide qué acciones tomar o a quiénes involucrar. El resultado es una arquitectura más clara donde los componentes permanecen enfocados en su responsabilidad.

25.2 Intención y motivación

La intención del patrón es definir un objeto que encapsule cómo un conjunto de objetos interactúan. Es apropiado cuando:

  • Existen dependencias muchos-a-muchos entre componentes que dificultan el mantenimiento.
  • Se desea modificar reglas de colaboración sin alterar cada colega.
  • La orquestación de eventos es compleja y se busca centralizarla para facilitar la lectura.
  • Se requiere habilitar nuevos flujos de comunicación sin reescribir objetos existentes.

Mediator favorece el principio de responsabilidad única: los colegas se enfocan en sus funciones y delegan la coordinación en el mediador.

25.3 Participantes y estructura

Los elementos esenciales son:

  • Mediator: interfaz o clase abstracta que declara las operaciones de coordinación.
  • ConcreteMediator: implementación que mantiene referencias a los colegas y decide cómo reaccionar ante sus notificaciones.
  • Colleague: componentes que interactúan a través del mediador.
  • ConcreteColleague: implementaciones que notifican eventos al mediador en lugar de comunicarse directamente.

En sistemas modernos es común inyectar el mediador en los colegas, facilitando pruebas y configuración.

25.4 Mediadores análogos y variantes

El patrón admite distintas variantes:

  • Mediador pasivo: solo rutea mensajes entre colegas.
  • Mediador orquestador: implementa lógica de negocio y toma decisiones.
  • Mediador distribuido: se implementa sobre un bus de eventos o colas para coordinar componentes remotos.

Seleccionar la variante adecuada evita que el mediador se convierta en un cuello de botella o en un "god object".

25.5 Escenario: tablero de soporte omnicanal

Un equipo de soporte opera un tablero donde convergen chats, correos y tickets. El analista debe ver el estado de cada canal sincronizado: si un ticket se marca como urgente, el chat se resalta y se notifica al supervisor; cuando el supervisor lo reasigna, el tablero actualiza la cola del analista.

Mediator permite centralizar esta coordinación: los widgets del tablero (lista de tickets, panel de chat, panel del supervisor) no se conocen entre ellos, sino que informan al mediador quien decide las actualizaciones a propagar.

25.6 Implementación en Java

El siguiente ejemplo implementa un mediador que orquesta la colaboración entre componentes de un tablero de soporte:

package tutorial.mediator;

import java.time.Instant;
import java.util.Objects;

public interface MediadorSoporte {
    void registrarTicket(Ticket ticket);
    void marcarUrgente(String idTicket);
    void reasignar(String idTicket, String nuevoAnalista);
    void registrarMensajeChat(String idTicket, String autor, String mensaje);
}

class MediadorSoporteImpl implements MediadorSoporte {
    private final PanelTickets panelTickets;
    private final PanelChat panelChat;
    private final PanelSupervisor panelSupervisor;

    MediadorSoporteImpl(PanelTickets panelTickets,
                        PanelChat panelChat,
                        PanelSupervisor panelSupervisor) {
        this.panelTickets = panelTickets;
        this.panelChat = panelChat;
        this.panelSupervisor = panelSupervisor;

        this.panelTickets.setMediador(this);
        this.panelChat.setMediador(this);
        this.panelSupervisor.setMediador(this);
    }

    @Override
    public void registrarTicket(Ticket ticket) {
        panelTickets.agregar(ticket);
        panelChat.crearSala(ticket);
        panelSupervisor.notificarNuevoTicket(ticket);
    }

    @Override
    public void marcarUrgente(String idTicket) {
        Ticket ticket = panelTickets.marcarUrgente(idTicket);
        if (ticket != null) {
            panelChat.destacarSala(idTicket);
            panelSupervisor.alertarPrioridad(idTicket);
        }
    }

    @Override
    public void reasignar(String idTicket, String nuevoAnalista) {
        panelTickets.actualizarResponsable(idTicket, nuevoAnalista);
        panelChat.registrarMensaje(idTicket, "Sistema",
                "Ticket reasignado a " + nuevoAnalista);
        panelSupervisor.confirmarReasignacion(idTicket, nuevoAnalista);
    }

    @Override
    public void registrarMensajeChat(String idTicket, String autor, String mensaje) {
        panelChat.registrarMensaje(idTicket, autor, mensaje);
        panelTickets.actualizarUltimaActividad(idTicket, Instant.now());
    }
}

abstract class ComponenteTablero {
    protected MediadorSoporte mediador;

    public void setMediador(MediadorSoporte mediador) {
        this.mediador = Objects.requireNonNull(mediador);
    }
}

class PanelTickets extends ComponenteTablero {
    private final java.util.Map<String, Ticket> tickets = new java.util.HashMap<>();

    public void agregar(Ticket ticket) {
        tickets.put(ticket.id(), ticket);
        System.out.println("[PanelTickets] registrado ticket " + ticket.id());
    }

    public Ticket marcarUrgente(String idTicket) {
        Ticket ticket = tickets.get(idTicket);
        if (ticket != null) {
            ticket.marcarUrgente();
            System.out.println("[PanelTickets] ticket " + idTicket + " marcado urgente");
        }
        return ticket;
    }

    public void actualizarResponsable(String idTicket, String nuevoResponsable) {
        Ticket ticket = tickets.get(idTicket);
        if (ticket != null) {
            ticket.actualizarResponsable(nuevoResponsable);
            System.out.println("[PanelTickets] ticket " + idTicket + " reasignado a " + nuevoResponsable);
        }
    }

    public void actualizarUltimaActividad(String idTicket, Instant instante) {
        Ticket ticket = tickets.get(idTicket);
        if (ticket != null) {
            ticket.actualizarUltimaActividad(instante);
            System.out.println("[PanelTickets] actividad registrada en " + idTicket);
        }
    }
}

class PanelChat extends ComponenteTablero {
    private final java.util.Set<String> salas = new java.util.HashSet<>();

    public void crearSala(Ticket ticket) {
        salas.add(ticket.id());
        System.out.println("[PanelChat] sala creada para " + ticket.id());
    }

    public void destacarSala(String idTicket) {
        if (salas.contains(idTicket)) {
            System.out.println("[PanelChat] sala " + idTicket + " destacada por urgencia");
        }
    }

    public void registrarMensaje(String idTicket, String autor, String mensaje) {
        if (salas.contains(idTicket)) {
            System.out.printf("[Chat %s] %s: %s%n", idTicket, autor, mensaje);
        }
    }
}

class PanelSupervisor extends ComponenteTablero {
    public void notificarNuevoTicket(Ticket ticket) {
        System.out.println("[Supervisor] Nuevo ticket asignado a " + ticket.responsable());
    }

    public void alertarPrioridad(String idTicket) {
        System.out.println("[Supervisor] Ticket " + idTicket + " requiere intervención urgente");
    }

    public void confirmarReasignacion(String idTicket, String nuevoAnalista) {
        System.out.println("[Supervisor] Ticket " + idTicket + " reasignado: " + nuevoAnalista);
    }
}

record Ticket(String id, String cliente, String responsable, Instant creada,
              boolean urgente, Instant ultimaActividad) {
    Ticket marcarUrgente() {
        return new Ticket(id, cliente, responsable, creada, true, ultimaActividad);
    }

    Ticket actualizarResponsable(String nuevoResponsable) {
        return new Ticket(id, cliente, nuevoResponsable, creada, urgente, ultimaActividad);
    }

    Ticket actualizarUltimaActividad(Instant instante) {
        return new Ticket(id, cliente, responsable, creada, urgente, instante);
    }
}

class AplicacionMediator {
    public static void main(String[] args) {
        PanelTickets panelTickets = new PanelTickets();
        PanelChat panelChat = new PanelChat();
        PanelSupervisor panelSupervisor = new PanelSupervisor();

        MediadorSoporte mediador = new MediadorSoporteImpl(panelTickets, panelChat, panelSupervisor);

        Ticket ticket = new Ticket("TCK-001", "Acme Corp", "Sofía", Instant.now(), false, Instant.now());
        mediador.registrarTicket(ticket);
        mediador.registrarMensajeChat("TCK-001", "Cliente", "Tengo un problema con la factura.");
        mediador.marcarUrgente("TCK-001");
        mediador.reasignar("TCK-001", "Marcos");
    }
}

25.7 Explicación del flujo

El mediador recibe las operaciones clave (registrar, marcar urgente, reasignar, mensajes) y coordina acciones entre los paneles. Los colegas se invocan a través del mediador, manteniéndose libres de referencias mutuas. Esto permite reemplazar paneles o agregar nuevos canales (por ejemplo, notificaciones push) modificando solo al mediador.

El estado de los tickets se encapsula en un record, lo que favorece la inmutabilidad; los paneles pueden actualizarlo devolviendo nuevas instancias si se requiere persistencia fuera del ejemplo.

25.8 Mediator en el ecosistema Java

Bibliotecas como JavaFX emplean mediadores para coordinar controles y escenas. La documentación de JOptionPane ilustra cómo un componente central coordina botones, iconos y textos sin que se referencien directamente. Asimismo, frameworks como Spring Integration implementan mediadores para enrutar mensajes entre canales.

25.9 Variantes y extensiones

Mediator se puede combinar con distintas estrategias de orquestación:

  • Event bus: el mediador actúa como fachada de un bus de eventos, publicando y suscribiéndose en nombre de los colegas.
  • Mediadores jerárquicos: un mediador coordina subsistemas y delega en mediadores especializados para dominios específicos.
  • Mediador declarativo: las reglas de coordinación se describen en configuraciones externas (JSON, YAML) para habilitar ajustes sin recompilar.

Estas variantes permiten adaptar el patrón a arquitecturas distribuidas o a soluciones que requieren alto nivel de configurabilidad.

25.10 Riesgos y malas prácticas

Un riesgo común es convertir al mediador en un “god object” que concentra demasiadas responsabilidades. También es problemático cuando los colegas conservan referencias cruzadas, lo que invalida el desacoplamiento buscado.

Otro anti-patrón es implementar mediadores que solo reenvían llamadas sin agregar coordinación real; en ese caso, es preferible usar Observer o simples invocaciones directas.

25.11 Buenas prácticas para aplicar Mediator

  • Modelar los eventos y comandos que el mediador expone con un lenguaje del dominio para facilitar la comunicación con el negocio.
  • Mantener al mediador enfocado en la coordinación y delegar lógica intensiva en servicios auxiliares.
  • Diseñar pruebas que verifiquen combinaciones críticas de interacciones entre colegas.
  • Documentar diagramas de colaboración que reflejen la orquestación centralizada.

25.12 Relación con otros patrones

Mediator se complementa con Observer cuando los colegas necesitan suscribirse a eventos generados por el mediador. Puede trabajar con Command encapsulando solicitudes que el mediador reenvía. Además, comparte similitudes con Facade al ofrecer un punto de acceso unificado, aunque Facade se centra en simplificar APIs y Mediator en coordinar interacciones entre colegas.