Loading learning content...
Pattern confusion is one of the most common obstacles in design pattern education. The Mediator and Facade patterns are frequently conflated because they share a surface-level similarity: both involve a single object that coordinates or simplifies interaction with multiple other objects.
This superficial resemblance masks fundamental differences in intent, behavior, and appropriate use cases. Misapplying these patterns leads to designs that solve the wrong problem, creating maintenance burdens rather than alleviating them.
This page provides a rigorous comparison of Mediator and Facade. You'll understand their different intents, architectural structures, communication patterns, and when to apply each. By the end, you'll confidently distinguish between them and apply the right pattern for each situation.
Design patterns are defined primarily by their intent—the problem they solve. Understanding intent is more important than memorizing structural details, because intent guides correct application.
| Pattern | Intent (GoF Definition) | Problem Solved |
|---|---|---|
| Mediator | Define an object that encapsulates how a set of objects interact | Complex, bidirectional many-to-many communication between peers |
| Facade | Provide a unified interface to a set of interfaces in a subsystem | Simplify access to a complex subsystem for external clients |
Let's unpack what these intents really mean:
Mediator's intent is about interaction encapsulation. The objects being mediated are peers that need to collaborate. They have ongoing, bidirectional relationships. The mediator manages how they work together, enforcing coordination rules and eliminating direct dependencies between them.
Facade's intent is about interface simplification. The objects behind the facade are subsystem components that together provide some capability. External clients don't need to understand the subsystem's internal structure—they just want to accomplish tasks. The facade provides a simple entry point.
Ask yourself: Are the objects peers that need to coordinate their behavior with each other? → Use Mediator. Are the objects components of a subsystem that external clients need simplified access to? → Use Facade.
While both patterns involve a central coordinating object, their internal structures differ significantly:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
// ═══════════════════════════════════════════════════════════════════// MEDIATOR STRUCTURE// ═══════════════════════════════════════════════════════════════════ /** * Mediator Pattern Structure * * Key characteristics: * 1. Colleagues know about the mediator (bidirectional reference) * 2. Mediator knows about all colleagues * 3. Colleagues communicate THROUGH mediator, not TO it for tasks * 4. Communication is bidirectional and ongoing */ interface ChatMediator { sendMessage(message: string, sender: ChatUser): void; addUser(user: ChatUser): void;} abstract class ChatUser { protected mediator: ChatMediator; protected name: string; constructor(name: string, mediator: ChatMediator) { this.name = name; this.mediator = mediator; this.mediator.addUser(this); // Register with mediator } // Outbound: user sends message THROUGH mediator public send(message: string): void { console.log(`${this.name} sends: ${message}`); this.mediator.sendMessage(message, this); } // Inbound: user receives message FROM mediator public abstract receive(message: string, from: string): void;} class RegularUser extends ChatUser { receive(message: string, from: string): void { console.log(`${this.name} received from ${from}: ${message}`); }} class ChatRoom implements ChatMediator { private users: ChatUser[] = []; addUser(user: ChatUser): void { this.users.push(user); } // The mediator's job: coordinate message distribution sendMessage(message: string, sender: ChatUser): void { for (const user of this.users) { if (user !== sender) { user.receive(message, sender.name); } } }} // KEY OBSERVATION: // - Users don't know about each other// - Users call mediator AND mediator calls users (bidirectional)// - Mediator contains coordination LOGIC (who gets what messages)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
// ═══════════════════════════════════════════════════════════════════// FACADE STRUCTURE// ═══════════════════════════════════════════════════════════════════ /** * Facade Pattern Structure * * Key characteristics: * 1. Subsystem components DON'T know about facade * 2. Facade knows about subsystem components * 3. Clients call facade TO accomplish tasks * 4. Communication is unidirectional (client → facade → subsystem) */ // Subsystem components - they exist independently of facadeclass VideoDecoder { decode(filename: string): ArrayBuffer { console.log(`Decoding video: ${filename}`); return new ArrayBuffer(1024); }} class AudioDecoder { decode(filename: string): ArrayBuffer { console.log(`Decoding audio: ${filename}`); return new ArrayBuffer(512); }} class VideoRenderer { render(videoData: ArrayBuffer): void { console.log(`Rendering video frame`); }} class AudioPlayer { play(audioData: ArrayBuffer): void { console.log(`Playing audio`); }} class SubtitleLoader { load(filename: string): string[] { console.log(`Loading subtitles`); return ["Subtitle line 1", "Subtitle line 2"]; }} /** * Facade provides simplified interface * * Notice: * - Subsystem classes have NO reference to facade * - Facade orchestrates but doesn't receive callbacks * - This is a one-way simplification layer */class VideoPlayerFacade { private videoDecoder: VideoDecoder; private audioDecoder: AudioDecoder; private videoRenderer: VideoRenderer; private audioPlayer: AudioPlayer; private subtitleLoader: SubtitleLoader; constructor() { this.videoDecoder = new VideoDecoder(); this.audioDecoder = new AudioDecoder(); this.videoRenderer = new VideoRenderer(); this.audioPlayer = new AudioPlayer(); this.subtitleLoader = new SubtitleLoader(); } /** * Simple interface for clients * Hides the complexity of coordinating multiple subsystem objects */ public playVideo(filename: string, withSubtitles: boolean = false): void { console.log(`\n▶️ Playing: ${filename}`); // Facade orchestrates subsystem const videoData = this.videoDecoder.decode(filename); const audioData = this.audioDecoder.decode(filename); if (withSubtitles) { const subs = this.subtitleLoader.load(filename); console.log(`Subtitles loaded: ${subs.length} lines`); } this.videoRenderer.render(videoData); this.audioPlayer.play(audioData); console.log(`✅ Video playing\n`); }} // Client code - simple and cleanconst player = new VideoPlayerFacade();player.playVideo("movie.mp4", true); // KEY OBSERVATION:// - Subsystem components don't know facade exists// - Facade calls subsystem, but subsystem never calls back// - Facade simplifies INTERFACE, not behavior coordinationThe communication patterns in Mediator and Facade are fundamentally different. Understanding these flows clarifies when each pattern applies.
In the Mediator pattern, communication flows in both directions between the mediator and its colleagues:
ColleagueA ←→ Mediator ←→ ColleagueB
↕
ColleagueC
Flow sequence:
This is dialogue, not just a request. Colleagues actively participate in ongoing coordination.
In the Facade pattern, communication flows in one direction from client through facade to subsystem:
Client → Facade → SubsystemA
→ SubsystemB
→ SubsystemC
Flow sequence:
This is delegation, not coordination. The facade acts as a simplified entry point to complex operations.
| Characteristic | Mediator | Facade |
|---|---|---|
| Direction | Bidirectional | Unidirectional |
| Initiator | Any colleague | External client only |
| Flow type | Event-driven dialogue | Request-response delegation |
| Ongoing? | Yes, continuous coordination | No, discrete requests |
| Callbacks | Common (mediator calls colleagues) | Rare (subsystem returns values) |
If objects behind your 'coordinator' call back to it with events → Mediator. If they just return values and never initiate communication → Facade.
Real-world examples illuminate the difference more clearly than abstract descriptions. Let's examine where each pattern naturally applies.
encode(input, format) method.generateReport(data) without exposing PDF internals.processPayment(order) method.compile(sourceCode) method.Notice that Mediator use cases involve ongoing, dynamic relationships between peers (users chat, controls react, aircraft navigate). Facade use cases involve one-time operations on subsystems (encode a video, generate a report, process a payment).
To truly understand the distinction, let's implement the same domain—a home automation system—using both patterns. This demonstrates how the choice depends on what problem you're solving.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
// ═══════════════════════════════════════════════════════════════════// HOME AUTOMATION WITH FACADE// Purpose: Simplify complex subsystem for external clients// ═══════════════════════════════════════════════════════════════════ // Subsystem components (don't know about facade)class LightingSystem { setLevel(room: string, level: number): void { console.log(` 💡 ${room} lights set to ${level}%`); }} class HVACSystem { setTemperature(temp: number): void { console.log(` 🌡️ Temperature set to ${temp}°F`); } setMode(mode: "heat" | "cool" | "auto"): void { console.log(` 🌡️ HVAC mode: ${mode}`); }} class SecuritySystem { arm(): void { console.log(` 🔒 Security armed`); } disarm(): void { console.log(` 🔓 Security disarmed`); }} class EntertainmentSystem { powerOn(): void { console.log(` 📺 Entertainment system on`); } powerOff(): void { console.log(` 📺 Entertainment system off`); }} class WindowBlinds { setPosition(position: number): void { console.log(` 🪟 Blinds set to ${position}%`); }} /** * Facade: Provides simple "scene" commands * * Clients don't need to know about individual subsystems. * They just want to set a mood or scenario. */class SmartHomeFacade { private lights: LightingSystem; private hvac: HVACSystem; private security: SecuritySystem; private entertainment: EntertainmentSystem; private blinds: WindowBlinds; constructor() { this.lights = new LightingSystem(); this.hvac = new HVACSystem(); this.security = new SecuritySystem(); this.entertainment = new EntertainmentSystem(); this.blinds = new WindowBlinds(); } // Simple facade methods hide subsystem complexity public goodMorning(): void { console.log("\n☀️ Good Morning Scene:"); this.blinds.setPosition(100); this.lights.setLevel("bedroom", 50); this.hvac.setTemperature(72); this.security.disarm(); } public leaveHome(): void { console.log("\n🚪 Leaving Home Scene:"); this.lights.setLevel("all", 0); this.entertainment.powerOff(); this.hvac.setTemperature(65); this.security.arm(); this.blinds.setPosition(0); } public movieNight(): void { console.log("\n🎬 Movie Night Scene:"); this.blinds.setPosition(0); this.lights.setLevel("living room", 10); this.entertainment.powerOn(); this.hvac.setTemperature(70); } public goodNight(): void { console.log("\n🌙 Good Night Scene:"); this.lights.setLevel("all", 0); this.entertainment.powerOff(); this.security.arm(); this.hvac.setTemperature(68); }} // Usage: Simple, command-like interfaceconst home = new SmartHomeFacade();home.goodMorning();home.movieNight();home.goodNight(); // Note: Subsystems never communicate with each other// Facade is just orchestrating one-time scene setups123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
// ═══════════════════════════════════════════════════════════════════// HOME AUTOMATION WITH MEDIATOR// Purpose: Coordinate ongoing interactions between smart devices// ═══════════════════════════════════════════════════════════════════ // Smart devices that need to coordinate their behavior interface SmartHomeMediator { notify(device: SmartDevice, event: string, data?: any): void;} abstract class SmartDevice { protected mediator: SmartHomeMediator; protected name: string; constructor(name: string, mediator: SmartHomeMediator) { this.name = name; this.mediator = mediator; } protected notifyChange(event: string, data?: any): void { this.mediator.notify(this, event, data); }} class MotionSensor extends SmartDevice { public detectMotion(room: string): void { console.log(`\n👁️ Motion detected in ${room}`); this.notifyChange("motion", { room }); } public noMotion(room: string): void { console.log(`\n😴 No motion in ${room} for 10 minutes`); this.notifyChange("no-motion", { room }); }} class SmartLight extends SmartDevice { private brightness: number = 0; public setBrightness(level: number): void { this.brightness = level; console.log(` 💡 ${this.name} set to ${level}%`); } public getBrightness(): number { return this.brightness; }} class SmartThermostat extends SmartDevice { private currentTemp: number = 70; private targetTemp: number = 70; public setTarget(temp: number): void { this.targetTemp = temp; console.log(` 🌡️ Target temperature: ${temp}°F`); this.notifyChange("target-changed", { temp }); } public reportTemperature(temp: number): void { this.currentTemp = temp; console.log(` 📊 Current temperature: ${temp}°F`); this.notifyChange("temp-reading", { temp, target: this.targetTemp }); }} class SmartBlinds extends SmartDevice { private position: number = 0; public setPosition(position: number): void { this.position = position; console.log(` 🪟 ${this.name} set to ${position}%`); this.notifyChange("blinds-changed", { position }); } public getPosition(): number { return this.position; }} class DoorSensor extends SmartDevice { public doorOpened(): void { console.log(`\n🚪 Front door opened`); this.notifyChange("door-opened", {}); } public doorClosed(): void { console.log(`\n🚪 Front door closed`); this.notifyChange("door-closed", {}); }} /** * Mediator coordinates device interactions * * Devices react to each other's events through the mediator. * This enables smart automation rules. */class SmartHomeController implements SmartHomeMediator { private motionSensor: MotionSensor; private livingRoomLight: SmartLight; private bedroomLight: SmartLight; private thermostat: SmartThermostat; private blinds: SmartBlinds; private doorSensor: DoorSensor; public initialize(): void { this.motionSensor = new MotionSensor("Motion Sensor", this); this.livingRoomLight = new SmartLight("Living Room Light", this); this.bedroomLight = new SmartLight("Bedroom Light", this); this.thermostat = new SmartThermostat("Thermostat", this); this.blinds = new SmartBlinds("Smart Blinds", this); this.doorSensor = new DoorSensor("Door Sensor", this); } /** * Central coordination logic - devices interact through here */ public notify(device: SmartDevice, event: string, data?: any): void { console.log(` 📬 Mediator received: ${event}`); // RULE: Motion detected → turn on lights in that room if (event === "motion") { const room = data.room; if (room === "living room") { this.livingRoomLight.setBrightness(80); } else if (room === "bedroom") { this.bedroomLight.setBrightness(50); } } // RULE: No motion → turn off lights after delay if (event === "no-motion") { const room = data.room; if (room === "living room") { this.livingRoomLight.setBrightness(0); } else if (room === "bedroom") { this.bedroomLight.setBrightness(0); } } // RULE: Temperature too high → open blinds to cool if (event === "temp-reading") { if (data.temp > data.target + 5) { console.log(` 🔥 Too hot! Opening blinds to cool down.`); this.blinds.setPosition(0); // Close blinds to block sun } } // RULE: Door opened → turn on entry lights if (event === "door-opened") { this.livingRoomLight.setBrightness(100); } // RULE: Blinds closed → may need artificial light if (event === "blinds-changed" && data.position === 0) { if (this.livingRoomLight.getBrightness() > 0) { // Boost light since natural light reduced console.log(` 💡 Boosting lights due to closed blinds`); this.livingRoomLight.setBrightness( Math.min(100, this.livingRoomLight.getBrightness() + 20) ); } } } // Accessors for simulation public getDevices() { return { motionSensor: this.motionSensor, thermostat: this.thermostat, doorSensor: this.doorSensor, blinds: this.blinds, }; }} // Usage: Ongoing device coordinationconst controller = new SmartHomeController();controller.initialize(); const { motionSensor, thermostat, doorSensor } = controller.getDevices(); // Simulate real-time device eventsmotionSensor.detectMotion("living room"); // → lights turn onthermostat.reportTemperature(82); // → blinds close → lights boostdoorSensor.doorOpened(); // → entry lights onmotionSensor.noMotion("living room"); // → lights turn offThe critical difference:
The Facade provides simple commands (movieNight(), goodMorning()) that orchestrate subsystems for one-time operations. Subsystems don't interact with each other.
The Mediator enables ongoing device interaction. When motion is detected, lights respond. When temperature changes, blinds adjust. Devices are peers in an ongoing coordination relationship.
In complex systems, you might need both patterns working together. This isn't pattern abuse—it's appropriate separation of concerns.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
// ═══════════════════════════════════════════════════════════════════// HYBRID: Facade + Mediator in Smart Home// ═══════════════════════════════════════════════════════════════════ /** * Scenario: Smart home has both: * 1. Simple scene commands for users (Facade use case) * 2. Ongoing device coordination (Mediator use case) * * Solution: Facade provides user interface, Mediator handles device coordination */ // The Mediator handles ongoing device interactions (as before)class DeviceCoordinator implements SmartHomeMediator { // ... device coordination logic ... notify(device: SmartDevice, event: string, data?: any): void { // Coordinate device interactions }} // The Facade provides simple user commands// It can ALSO trigger mediator-coordinated behaviorsclass SmartHomeUserInterface { private deviceCoordinator: DeviceCoordinator; constructor(coordinator: DeviceCoordinator) { this.deviceCoordinator = coordinator; } // Facade methods for simple user commands public activateMovieMode(): void { console.log("\n🎬 Activating Movie Mode..."); // Some actions are simple facade orchestration // (subsystems don't need to coordinate) this.deviceCoordinator.setLights("living room", 10); this.deviceCoordinator.closeBlinds(); this.deviceCoordinator.powerOnTV(); // But we also switch the mediator to "movie mode" // which affects ongoing coordination rules this.deviceCoordinator.setMode("movie"); // Now motion sensors won't turn lights on high, etc. } public activateAwayMode(): void { console.log("\n🚪 Activating Away Mode..."); // Set everything to away state this.deviceCoordinator.setAllLights(0); this.deviceCoordinator.armSecurity(); // Switch coordination mode this.deviceCoordinator.setMode("away"); // Now motion triggers security alerts instead of lights }} // Usage:// - User interacts with Facade (simple commands)// - Devices interact through Mediator (ongoing coordination)// - Facade can influence Mediator's behavior (change modes)| Concern | Pattern Used | Responsibility |
|---|---|---|
| User commands | Facade | Simple scene activation, mode switching |
| Device coordination | Mediator | Ongoing interaction rules, event handling |
| External API | Facade | Hide internal complexity from apps/voice assistants |
| Automation rules | Mediator | Manage complex multi-device behaviors |
Don't be afraid to use multiple patterns in the same system. The key is using each pattern for its intended purpose. Facades simplify access; Mediators coordinate behavior. When you need both, implement both.
Use this decision framework when you're unsure which pattern applies:
| Indicator | Choose Mediator | Choose Facade |
|---|---|---|
| Object-coordinator relationship | Bidirectional | Unidirectional (coordinator → objects) |
| Objects aware of coordinator | Yes | No |
| Primary problem | Object coupling | Interface complexity |
| Interaction duration | Ongoing | Per-request |
| Typical examples | Chat rooms, dialogs, game entities | Libraries, SDKs, complex subsystems |
Don't choose based on superficial structure. A class that 'coordinates' multiple objects could be either pattern. Always analyze the communication flow and intent.
We've thoroughly compared the Mediator and Facade patterns. Let's consolidate the key distinctions:
What's next:
With the Mediator vs Facade distinction clear, the final page explores real-world use cases of the Mediator Pattern. We'll examine implementations in chat systems, event-driven architectures, and UI frameworks—showing how the pattern scales from simple dialogs to enterprise systems.
You now understand the critical distinction between Mediator and Facade patterns. Mediator coordinates peer behavior through bidirectional communication; Facade simplifies subsystem access through unidirectional delegation. Use the decision framework to choose correctly.