Mediator Pattern para Desacoplamiento de Componentes en
Reduce dependencias caoticas entre componentes UI introduciendo un mediador que centraliza comunicacion, previniendo referencias explicitas entre pares
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Mediator Pattern para Desacoplamiento de Componentes en Apps Frontend
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
FAQ
P: En que se diferencia de Observer? R: Observer es broadcast one-to-many. Mediator es many-to-many enrutado a traves de un coordinador central.
P: Cuando deberia usar un state manager en su lugar? R: Cuando la necesidad primaria es estado compartido, no solo comunicacion. Mediator maneja mensajes; state managers manejan datos.
¿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.
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. Recursos Relacionados
Observer Pattern
Define a subscription mechanism to notify multiple objects about events. A behavioral design pattern for event-driven communication.
PatternFacade Pattern
Provide a simplified interface to a complex subsystem. A structural pattern that hides implementation details behind a clean API.
PatternBackend for Frontend (BFF) Pattern
Create dedicated backend services tailored to the specific needs of each frontend client type, aggregating downstream APIs and optimizing data shapes per platform.
PatternInterpreter Pattern for Domain-Specific Expression Languages
Build a language interpreter that evaluates expressions and rules by representing grammar as composable objects, useful for formulas, queries, and business rules
PatternAbstract Factory for Cross-Platform UI Component Families
Create families of related objects without specifying concrete classes, enabling platform-specific implementations that share a common interface
PatternBridge Pattern for Decoupling UI Components from Themes
Separate an abstraction from its implementation so both can vary independently using the Bridge pattern for pluggable UI themes and rendering engines
PatternComposite Pattern for UI Component Trees in React
Use the Composite pattern to compose objects into tree structures, letting clients treat individual objects and compositions uniformly in UI component hierarchies