Loading learning content...
Theory becomes mastery through application. The Mediator Pattern appears across diverse domains—from real-time messaging systems handling millions of concurrent users to local GUI applications coordinating form controls. Understanding these applications reveals the pattern's versatility and guides correct implementation.
This page presents comprehensive implementations of the Mediator Pattern in five real-world domains: messaging systems, event-driven architectures, UI frameworks, game development, and microservices orchestration.
Chat rooms are the canonical Mediator example. Users must communicate without knowing about each other, messages must be distributed according to rules (broadcast, private, channel-specific), and the system must handle dynamic membership.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
// ═══════════════════════════════════════════════════════════════════// CHAT ROOM MEDIATOR - Production-Ready Implementation// ═══════════════════════════════════════════════════════════════════ interface ChatMediator { sendMessage(message: Message, sender: User): void; sendPrivateMessage(message: Message, sender: User, recipientId: string): void; userJoined(user: User): void; userLeft(user: User): void;} interface Message { content: string; timestamp: Date; type: "text" | "image" | "system";} abstract class User { protected mediator: ChatMediator; public readonly id: string; public readonly displayName: string; protected status: "online" | "away" | "dnd" = "online"; constructor(id: string, name: string, mediator: ChatMediator) { this.id = id; this.displayName = name; this.mediator = mediator; } public sendMessage(content: string): void { const message: Message = { content, timestamp: new Date(), type: "text" }; this.mediator.sendMessage(message, this); } public sendPrivateMessage(content: string, recipientId: string): void { const message: Message = { content, timestamp: new Date(), type: "text" }; this.mediator.sendPrivateMessage(message, this, recipientId); } public abstract receiveMessage(message: Message, from: User): void; public abstract receiveSystemNotification(notification: string): void;} class StandardUser extends User { private messageHistory: Array<{message: Message, from: string}> = []; receiveMessage(message: Message, from: User): void { this.messageHistory.push({ message, from: from.displayName }); console.log(`[${this.displayName}] received from ${from.displayName}: ${message.content}`); } receiveSystemNotification(notification: string): void { console.log(`[${this.displayName}] SYSTEM: ${notification}`); }} class ChatRoom implements ChatMediator { private users: Map<string, User> = new Map(); private bannedUsers: Set<string> = new Set(); private messageRateLimit: Map<string, number[]> = new Map(); userJoined(user: User): void { if (this.bannedUsers.has(user.id)) { console.log(`Banned user ${user.displayName} attempted to join`); return; } this.users.set(user.id, user); // Notify all existing users const notification = `${user.displayName} joined the chat`; this.users.forEach((u) => { if (u.id !== user.id) { u.receiveSystemNotification(notification); } }); } userLeft(user: User): void { this.users.delete(user.id); const notification = `${user.displayName} left the chat`; this.users.forEach((u) => u.receiveSystemNotification(notification)); } sendMessage(message: Message, sender: User): void { // Rate limiting check if (!this.checkRateLimit(sender.id)) { sender.receiveSystemNotification("Rate limit exceeded. Please slow down."); return; } // Broadcast to all users except sender this.users.forEach((user) => { if (user.id !== sender.id) { user.receiveMessage(message, sender); } }); } sendPrivateMessage(message: Message, sender: User, recipientId: string): void { const recipient = this.users.get(recipientId); if (recipient) { recipient.receiveMessage(message, sender); } else { sender.receiveSystemNotification(`User ${recipientId} not found`); } } private checkRateLimit(userId: string): boolean { const now = Date.now(); const userMessages = this.messageRateLimit.get(userId) || []; const recentMessages = userMessages.filter(t => now - t < 60000); if (recentMessages.length >= 30) return false; recentMessages.push(now); this.messageRateLimit.set(userId, recentMessages); return true; }}In event-driven systems, the Mediator Pattern manifests as event buses, message brokers, and orchestrators. The mediator receives events and coordinates responses across multiple services or components.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
// ═══════════════════════════════════════════════════════════════════// TYPE-SAFE EVENT BUS MEDIATOR// ═══════════════════════════════════════════════════════════════════ // Type-safe event definitionsinterface EventMap { "user:registered": { userId: string; email: string; timestamp: Date }; "order:placed": { orderId: string; userId: string; amount: number }; "payment:completed": { orderId: string; transactionId: string }; "inventory:low": { productId: string; currentStock: number };} type EventName = keyof EventMap; interface EventHandler<T extends EventName> { handle(event: EventMap[T]): void | Promise<void>;} class EventBusMediator { private handlers: Map<EventName, Set<EventHandler<any>>> = new Map(); private eventLog: Array<{event: EventName; data: any; timestamp: Date}> = []; subscribe<T extends EventName>(eventName: T, handler: EventHandler<T>): void { if (!this.handlers.has(eventName)) { this.handlers.set(eventName, new Set()); } this.handlers.get(eventName)!.add(handler); } unsubscribe<T extends EventName>(eventName: T, handler: EventHandler<T>): void { this.handlers.get(eventName)?.delete(handler); } async publish<T extends EventName>(eventName: T, data: EventMap[T]): Promise<void> { // Log for audit this.eventLog.push({ event: eventName, data, timestamp: new Date() }); const handlers = this.handlers.get(eventName); if (!handlers) return; // Execute handlers (could be parallel or sequential based on needs) const promises = Array.from(handlers).map(handler => handler.handle(data)); await Promise.all(promises); }} // Service implementationsclass EmailService implements EventHandler<"user:registered"> { handle(event: EventMap["user:registered"]): void { console.log(`📧 Sending welcome email to ${event.email}`); }} class AnalyticsService implements EventHandler<"order:placed"> { handle(event: EventMap["order:placed"]): void { console.log(`📊 Recording order ${event.orderId} for analytics`); }} class InventoryService implements EventHandler<"order:placed"> { private eventBus: EventBusMediator; constructor(eventBus: EventBusMediator) { this.eventBus = eventBus; } handle(event: EventMap["order:placed"]): void { console.log(`📦 Reserving inventory for order ${event.orderId}`); // May trigger its own events this.eventBus.publish("inventory:low", { productId: "SKU-123", currentStock: 5 }); }} // Usageconst eventBus = new EventBusMediator();eventBus.subscribe("user:registered", new EmailService());eventBus.subscribe("order:placed", new AnalyticsService());eventBus.subscribe("order:placed", new InventoryService(eventBus));Modern UI frameworks like React use Mediator-like patterns for state management. Redux, MobX, and Zustand all centralize state coordination—components notify the store of changes, and the store coordinates updates to all subscribed components.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
// ═══════════════════════════════════════════════════════════════════// STATE MANAGEMENT MEDIATOR (Redux-like pattern)// ═══════════════════════════════════════════════════════════════════ interface Action { type: string; payload?: any;} type Reducer<S> = (state: S, action: Action) => S;type Subscriber<S> = (state: S) => void; class StateMediator<S> { private state: S; private reducer: Reducer<S>; private subscribers: Set<Subscriber<S>> = new Set(); constructor(initialState: S, reducer: Reducer<S>) { this.state = initialState; this.reducer = reducer; } getState(): S { return this.state; } dispatch(action: Action): void { console.log(`📤 Dispatching: ${action.type}`); // Reduce to new state const newState = this.reducer(this.state, action); // Only notify if state changed if (newState !== this.state) { this.state = newState; this.notifySubscribers(); } } subscribe(subscriber: Subscriber<S>): () => void { this.subscribers.add(subscriber); // Return unsubscribe function return () => this.subscribers.delete(subscriber); } private notifySubscribers(): void { this.subscribers.forEach(sub => sub(this.state)); }} // Application stateinterface AppState { user: { name: string; loggedIn: boolean } | null; cart: Array<{ id: string; quantity: number }>; notifications: string[];} // Reducer (pure function handling state transitions)const appReducer: Reducer<AppState> = (state, action) => { switch (action.type) { case "LOGIN": return { ...state, user: action.payload }; case "ADD_TO_CART": return { ...state, cart: [...state.cart, action.payload] }; case "SHOW_NOTIFICATION": return { ...state, notifications: [...state.notifications, action.payload] }; default: return state; }}; // Usageconst store = new StateMediator<AppState>( { user: null, cart: [], notifications: [] }, appReducer); // Components subscribe to state changesstore.subscribe((state) => { console.log(`Header updated: user = ${state.user?.name || "Guest"}`);}); store.subscribe((state) => { console.log(`Cart badge: ${state.cart.length} items`);}); // Components dispatch actionsstore.dispatch({ type: "LOGIN", payload: { name: "Alice", loggedIn: true } });store.dispatch({ type: "ADD_TO_CART", payload: { id: "item-1", quantity: 2 } });In games, entities (players, NPCs, projectiles, items) constantly interact. Without mediation, entity classes become tightly coupled and hard to modify. A game event mediator coordinates interactions cleanly.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
// ═══════════════════════════════════════════════════════════════════// GAME EVENT MEDIATOR// ═══════════════════════════════════════════════════════════════════ interface GameEvent { type: string; source: GameEntity; data: any;} interface GameMediator { notify(event: GameEvent): void; register(entity: GameEntity): void;} abstract class GameEntity { protected mediator: GameMediator; public readonly id: string; public position: { x: number; y: number }; public health: number; constructor(id: string, mediator: GameMediator) { this.id = id; this.mediator = mediator; this.position = { x: 0, y: 0 }; this.health = 100; mediator.register(this); } protected emit(type: string, data: any): void { this.mediator.notify({ type, source: this, data }); } abstract handleEvent(event: GameEvent): void;} class Player extends GameEntity { public score: number = 0; attack(): void { this.emit("player:attack", { damage: 25, range: 50 }); } handleEvent(event: GameEvent): void { if (event.type === "enemy:defeated") { this.score += event.data.points; console.log(`Player score: ${this.score}`); } if (event.type === "item:collected" && event.data.type === "health") { this.health = Math.min(100, this.health + event.data.value); } }} class Enemy extends GameEntity { takeDamage(amount: number): void { this.health -= amount; if (this.health <= 0) { this.emit("enemy:defeated", { points: 100 }); } } handleEvent(event: GameEvent): void { if (event.type === "player:attack") { const distance = this.calculateDistance(event.source.position); if (distance <= event.data.range) { this.takeDamage(event.data.damage); } } } private calculateDistance(pos: {x: number; y: number}): number { return Math.sqrt(Math.pow(pos.x - this.position.x, 2) + Math.pow(pos.y - this.position.y, 2)); }} class GameManager implements GameMediator { private entities: Map<string, GameEntity> = new Map(); register(entity: GameEntity): void { this.entities.set(entity.id, entity); } notify(event: GameEvent): void { console.log(`🎮 Event: ${event.type}`); this.entities.forEach((entity) => { if (entity.id !== event.source.id) { entity.handleEvent(event); } }); }}At system scale, the Mediator Pattern manifests as orchestration services. When business processes span multiple microservices, an orchestrator coordinates the workflow, handling successes, failures, and compensating actions.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
// ═══════════════════════════════════════════════════════════════════// ORDER PROCESSING ORCHESTRATOR// ═══════════════════════════════════════════════════════════════════ interface ServiceResult { success: boolean; error?: string; data?: any;} // Services don't know about each otherclass InventoryService { async reserveItems(items: any[]): Promise<ServiceResult> { console.log("📦 Reserving inventory..."); return { success: true, data: { reservationId: "RES-123" } }; } async releaseReservation(reservationId: string): Promise<void> { console.log(`📦 Releasing reservation ${reservationId}`); }} class PaymentService { async processPayment(amount: number): Promise<ServiceResult> { console.log(`💳 Processing payment of $${amount}...`); return { success: true, data: { transactionId: "TXN-456" } }; } async refund(transactionId: string): Promise<void> { console.log(`💳 Refunding transaction ${transactionId}`); }} class ShippingService { async scheduleShipment(orderId: string): Promise<ServiceResult> { console.log(`🚚 Scheduling shipment for ${orderId}...`); return { success: true, data: { trackingNumber: "TRACK-789" } }; }} // Orchestrator mediates the entire workflowclass OrderOrchestrator { constructor( private inventory: InventoryService, private payment: PaymentService, private shipping: ShippingService ) {} async processOrder(order: {id: string; items: any[]; amount: number}): Promise<ServiceResult> { console.log(`\n🛒 Processing order ${order.id}`); // Step 1: Reserve inventory const inventoryResult = await this.inventory.reserveItems(order.items); if (!inventoryResult.success) { return { success: false, error: "Inventory reservation failed" }; } // Step 2: Process payment const paymentResult = await this.payment.processPayment(order.amount); if (!paymentResult.success) { // Compensating action: release inventory await this.inventory.releaseReservation(inventoryResult.data.reservationId); return { success: false, error: "Payment failed" }; } // Step 3: Schedule shipping const shippingResult = await this.shipping.scheduleShipment(order.id); if (!shippingResult.success) { // Compensating actions await this.payment.refund(paymentResult.data.transactionId); await this.inventory.releaseReservation(inventoryResult.data.reservationId); return { success: false, error: "Shipping failed" }; } console.log(`✅ Order ${order.id} completed successfully`); return { success: true, data: { orderId: order.id, reservationId: inventoryResult.data.reservationId, transactionId: paymentResult.data.transactionId, trackingNumber: shippingResult.data.trackingNumber } }; }}If your mediator grows to thousands of lines with complex nested conditionals, consider splitting into multiple specialized mediators or moving some logic back to colleagues. The pattern should simplify, not shift complexity.
You've mastered the Mediator Pattern—from understanding the problem of complex object interactions, through implementing centralized mediators, distinguishing from Facade, to applying in real-world scenarios. You can now confidently apply this pattern to transform chaotic many-to-many communications into elegant, maintainable systems.