Facade Pattern
The Facade Pattern provides a simplified interface to a complex subsystem of classes, library, or framework. It doesn't encapsulate the subsystem but provides a unified interface that makes the subsystem easier to use.
By defining a higher-level interface that makes the subsystem easier to use, the Facade Pattern helps to decouple a client from a complex subsystem.
Problem
Imagine you have a complex system with many moving parts, and you need to use only a small portion of its functionality. This might be a third-party library, a large framework, or just a subset of your own complex codebase. How do you provide a simple interface for common use cases without exposing all the underlying complexity?
Components
- Facade: Provides a simplified interface to a complex subsystem.
- Subsystem classes: Implement the functionality of the subsystem and handle work assigned by the Facade. They don't know about the Facade.
- Client: Uses the Facade instead of accessing the subsystem objects directly.
Implementation Example
Let's implement a Home Theater Facade that simplifies the operations of a complex home theater system:
// Subsystem components
class TV {
turnOn() {
console.log("TV is turned on");
}
turnOff() {
console.log("TV is turned off");
}
setInput(input) {
console.log(`TV input set to ${input}`);
}
}
class AudioSystem {
turnOn() {
console.log("Audio system is turned on");
}
turnOff() {
console.log("Audio system is turned off");
}
setVolume(level) {
console.log(`Audio volume set to ${level}`);
}
setSurroundSound() {
console.log("Surround sound enabled");
}
}
class StreamingPlayer {
turnOn() {
console.log("Streaming player is turned on");
}
turnOff() {
console.log("Streaming player is turned off");
}
play(movie) {
console.log(`Playing "${movie}"`);
}
stop() {
console.log("Stopped playback");
}
}
class Lights {
dim(level) {
console.log(`Lights dimmed to ${level}%`);
}
brighten() {
console.log("Lights brightened to 100%");
}
}
// Facade
class HomeTheaterFacade {
constructor(tv, audio, streamingPlayer, lights) {
this.tv = tv;
this.audio = audio;
this.streamingPlayer = streamingPlayer;
this.lights = lights;
}
watchMovie(movie) {
console.log("Get ready to watch a movie...");
this.lights.dim(10);
this.tv.turnOn();
this.audio.turnOn();
this.audio.setSurroundSound();
this.audio.setVolume(5);
this.streamingPlayer.turnOn();
this.tv.setInput("HDMI1");
this.streamingPlayer.play(movie);
}
endMovie() {
console.log("Shutting down the home theater...");
this.streamingPlayer.stop();
this.streamingPlayer.turnOff();
this.audio.turnOff();
this.tv.turnOff();
this.lights.brighten();
}
}
// Client code
const tv = new TV();
const audio = new AudioSystem();
const streamingPlayer = new StreamingPlayer();
const lights = new Lights();
const homeTheater = new HomeTheaterFacade(tv, audio, streamingPlayer, lights);
// Simple interface for the client
homeTheater.watchMovie("Inception");
// Later...
homeTheater.endMovie();
Interactive Demo: Home Theater Facade
Experience how the Facade pattern simplifies controlling a complex home theater system.
Without Facade (Complex Direct Usage):
When to Use
- When you need to provide a simple interface to a complex subsystem
- When there are many dependencies between clients and implementation classes
- When you want to layer your system and use facade as entry points to each layer
- When you need to decouple a client from a complex subsystem
Benefits
- Reduces complexity and makes subsystems easier to use
- Promotes weak coupling between clients and subsystems
- Shields clients from components of a subsystem that might change
- Simplifies large, complex systems into more manageable layers
- Helps promote the principle of least knowledge (Law of Demeter)
Real-World Uses
- jQuery library providing a simpler interface to complex browser APIs
- Service facades in enterprise applications
- APIs that hide complex underlying functionality
- Unified interfaces for complex device systems like home theaters, smart homes
- Simplified interfaces for complex subsystems like database or payment processing