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.
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.
La intención del patrón es definir un objeto que encapsule cómo un conjunto de objetos interactúan. Es apropiado cuando:
Mediator favorece el principio de responsabilidad única: los colegas se enfocan en sus funciones y delegan la coordinación en el mediador.
Los elementos esenciales son:
En sistemas modernos es común inyectar el mediador en los colegas, facilitando pruebas y configuración.
El patrón admite distintas variantes:
Seleccionar la variante adecuada evita que el mediador se convierta en un cuello de botella o en un "god object".
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.
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");
}
}
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.
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.
Mediator se puede combinar con distintas estrategias de orquestación:
Estas variantes permiten adaptar el patrón a arquitecturas distribuidas o a soluciones que requieren alto nivel de configurabilidad.
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.
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.