Mediator Pattern para Desacoplamiento de Componentes en
Reduce dependencias caoticas entre componentes UI introduciendo un mediador que centraliza comunicacion, previniendo referencias explicitas entre pares
El Mediator pattern define un objeto que encapsula como un conjunto de objetos interactuan. En lugar de que componentes se referencien entre si directamente, se refieren a un mediador, reduciendo el numero de conexiones explicitas de many-to-many a many-to-one. Esto es esencial para UIs complejas donde docenas de componentes necesitan mantenerse sincronizados.
Cuando Usar Esto
- Los componentes tienen relaciones many-to-many que de otro modo crearian acoplamiento fuerte
- Reusar componentes independientemente es dificil porque dependen de pares especificos
- La logica de comunicacion esta dispersa y dificil de testear
Problema
Un dashboard con filtros, graficos, tablas y mapas requiere que cada widget notifique a todos los otros cuando los datos cambian. Cada widget mantiene referencias a 5-6 otros, creando una pesadilla de dependencias.
Solucion
// mediator/DashboardMediator.ts
interface Mediator {
notify(sender: Component, event: string, data?: unknown): void;
}
abstract class Component {
constructor(protected mediator: Mediator) {}
send(event: string, data?: unknown): void {
this.mediator.notify(this, event, data);
}
}
class FilterPanel extends Component {
private selectedRegion = 'all';
selectRegion(region: string): void {
this.selectedRegion = region;
this.send('region-changed', region);
}
}
class ChartWidget extends Component {
private data: unknown[] = [];
updateData(data: unknown[]): void {
this.data = data;
this.render();
}
private render(): void {
console.log('Chart rendered with', this.data.length, 'points');
}
}
class TableWidget extends Component {
private rows: unknown[] = [];
updateRows(rows: unknown[]): void {
this.rows = rows;
console.log('Table updated with', rows.length, 'rows');
}
}
class MapWidget extends Component {
private center = { lat: 0, lng: 0 };
panTo(center: { lat: number; lng: number }): void {
this.center = center;
console.log('Map centered at', center);
}
}
// Mediator orquesta toda la comunicacion
class DashboardMediator implements Mediator {
private filters: FilterPanel;
private chart: ChartWidget;
private table: TableWidget;
private map: MapWidget;
setComponents(
filters: FilterPanel,
chart: ChartWidget,
table: TableWidget,
map: MapWidget
): void {
this.filters = filters;
this.chart = chart;
this.table = table;
this.map = map;
}
notify(sender: Component, event: string, data?: unknown): void {
switch (event) {
case 'region-changed': {
const filteredData = this.fetchDataForRegion(data as string);
this.chart.updateData(filteredData);
this.table.updateRows(filteredData);
this.map.panTo(this.getRegionCenter(data as string));
break;
}
case 'chart-point-clicked': {
const point = data as { lat: number; lng: number };
this.map.panTo(point);
break;
}
}
}
private fetchDataForRegion(region: string): unknown[] {
return [{ id: 1, region }];
}
private getRegionCenter(region: string): { lat: number; lng: number } {
const centers: Record<string, { lat: number; lng: number }> = {
'north': { lat: 45, lng: 0 },
'south': { lat: -45, lng: 0 },
};
return centers[region] || { lat: 0, lng: 0 };
}
}
// Uso
const mediator = new DashboardMediator();
const filters = new FilterPanel(mediator);
const chart = new ChartWidget(mediator);
const table = new TableWidget(mediator);
const map = new MapWidget(mediator);
mediator.setComponents(filters, chart, table, map);
filters.selectRegion('north');
Variacion: Event Bus Mediator
// mediator/EventBus.ts
class EventBus implements Mediator {
private listeners = new Map<string, Set<(data: unknown) => void>>();
subscribe(event: string, callback: (data: unknown) => void): () => void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(callback);
return () => this.listeners.get(event)?.delete(callback);
}
notify(_sender: Component, event: string, data?: unknown): void {
this.listeners.get(event)?.forEach(cb => cb(data));
}
emit(event: string, data?: unknown): void {
this.notify(null as unknown as Component, event, data);
}
}
const bus = new EventBus();
bus.subscribe('user-login', user => console.log('Logged in:', user));
bus.emit('user-login', { id: 1 });
Como Funciona
- Mediator declara la interfaz de comunicacion
- Concrete Mediator implementa logica de coordinacion entre colegas
- Colleague componentes envian eventos al mediador en lugar de entre si
- Client crea y conecta el mediador con todos los colegas
Consideraciones de Produccion
- Manten mediadores enfocados en un dominio; no crees un god object
- Usa eventos tipados para prevenir bugs de comunicacion stringly-typed
- Considera librerias de state management (Redux, Zustand) como mediadores evolucionados. Consulta Singleton para gestion de instancias de servicios.
Errores Comunes
- Crear un mediador tan grande que se vuelve inmantenible
- Saltearse el mediador para comunicacion directa entre componentes
- No desuscribir listeners de eventos, causando memory leaks
Troubleshooting
- Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
- Tests break when the design changes: favor stable contracts over internal structure.
- Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.
Temas Avanzados
Escenario: Mediator para Chat Room
// Mediator pattern: centralizar comunicacion entre componentes
interface ChatMediator {
sendMessage(msg: string, user: User): void;
registerUser(user: User): void;
}
abstract class User {
constructor(protected mediator: ChatMediator, public name: string) {}
abstract send(msg: string): void;
abstract receive(msg: string): void;
}
class ChatUser extends User {
send(msg: string) { this.mediator.sendMessage(msg, this); }
receive(msg: string) { console.log(`[${this.name}] received: ${msg}`); }
}
// Mediator concreto
class ChatRoom implements ChatMediator {
private users: User[] = [];
registerUser(user: User) { this.users.push(user); }
sendMessage(msg: string, sender: User) {
// Broadcast a todos excepto el emisor
this.users.filter(u => u !== sender).forEach(u => u.receive(msg));
}
}
// Uso: usuarios no se conocen entre si
const chat = new ChatRoom();
const alice = new ChatUser(chat, "Alice");
const bob = new ChatUser(chat, "Bob");
const charlie = new ChatUser(chat, "Charlie");
chat.registerUser(alice);
chat.registerUser(bob);
chat.registerUser(charlie);
alice.send("Hola a todos");
// [Bob] received: Hola a todos
// [Charlie] received: Hola a todos
// Sin Mediator: cada usuario necesita referencia a los demas
// Con Mediator: solo conocen al mediator
Lecciones:
- Mediator centraliza comunicacion: componentes no se conocen
- Anadir nuevo usuario no requiere cambiar los existentes
- El mediator puede filtrar, transformar o logear mensajes
- Reduce acoplamiento de N*(N-1) a N*1 (cada uno solo conoce al mediator)
- CQRS usa mediator: commands y queries van a traves de un bus
- Event bus es una forma de mediator con pub/sub
### Mediator vs Observer: cual uso?
Mediator centraliza: los componentes hablan al mediator y este redirige. Observer descentraliza: el sujeto notifica a observadores directamente. Usa Mediator cuando la logica de interaccion es compleja y quieres centralizarla (chat room, wizard form). Usa Observer cuando solo necesitas notificar cambios (data binding, event handling). Mediator conoce a los componentes; Observer no.
End of document. Review and update quarterly.
## Lectura Adicional
- **Documentación oficial**: consulta la referencia actualizada del framework o herramienta utilizada.
- **Guías relacionadas**: explora las guías de mediator y behavioral-patterns para profundizar.
- **Patrones complementarios**: revisa los patrones de diseño aplicables a tu stack tecnológico.
- **Postmortems públicos**: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
## Notas de Producción
- **Despliega gradualmente** usando canary o blue-green para detectar regresiones temprano.
- **Configura alertas** para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- **Documenta el rollback** en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- **Revisa logs estructurados** con correlation IDs para trazar requests end-to-end en incidentes.
## Puntos Clave
- **Aplica mediator pattern para desacoplamiento de componentes en** cuando necesites una solución práctica para tu caso de uso.
- **Monitorea el rendimiento** después de implementar; mide latencia, errores y uso de recursos antes y después.
- **Revisa la sección de Troubleshooting** ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- **Mantén dependencias actualizadas** y ejecuta tests en CI para prevenir regresiones en producción.
## Errores Comunes en Producción
- Aplicar el patrón donde no se necesita abstracción, agregando complejidad accidental.
- Dejar que el patrón se filtre en módulos no relacionados y confundir los límites de responsabilidad.
- Sobre-ingeniería en la primera implementación en lugar de comenzar simple y medir el dolor.
- Saltar los tests de contrato, de modo que las refactorizaciones rompan consumidores en silencio.
- Ignorar modos de fallo que el patrón no cubre.
- Usar el patrón como opción por defecto en lugar de elegir la herramienta adecuada para la escala actual.
- Olvidar documentar cuándo dejar de usar el patrón y qué lo reemplaza.
- Carecer de observabilidad sobre rendimiento y propagación de errores del patrón. Preguntas frecuentes
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Recursos Relacionados
Patrón Observer
Define un mecanismo de suscripción para notificar a múltiples objetos sobre eventos. Patrón de diseño conductual para comunicación basada en eventos.
PatternPatrón Facade
Provee una interfaz simplificada a un subsistema complejo. Un patrón estructural que oculta detalles de implementación detrás de una API limpia.
PatternPatrón Backend for Frontend (BFF)
Crea servicios backend dedicados adaptados a las necesidades específicas de cada tipo de frontend client, agregando APIs downstream y optimizando formas de datos por plataforma.
PatternInterpreter Pattern para Lenguajes de Expresion
Construye un interprete de lenguaje que evalua expresiones y reglas representando la gramatica como objetos componibles, util para formulas, queries y reglas de negocio
PatternAbstract Factory para Familias de Componentes UI
Crea familias de objetos relacionados sin especificar clases concretas, habilitando implementaciones especificas de plataforma que comparten una interfaz comun
PatternBridge Pattern para Desacoplar Componentes UI de Temas
Separa una abstraccion de su implementacion para que ambas puedan variar independientemente usando el Bridge pattern para temas UI y motores de renderizado intercambiables