In software development, it’s not uncommon to encounter complex systems with numerous subsystems and interdependencies. Managing and interacting with such systems can become a daunting task. This is where the Facade Pattern comes to the rescue. The Facade Pattern provides a unified interface to a set of interfaces in a subsystem, simplifying the usage and understanding of the system as a whole.
🙋🏻♀️ What is the Facade Pattern?
The Facade Pattern is a structural design pattern that provides a simplified interface to a complex system, making it easier to use and understand. It acts as a high-level interface that hides the complexities of the underlying subsystem.
The main idea behind the Facade Pattern is to create a facade
class that encapsulates the interactions with the subsystem’s components, shielding the client from the details of individual subsystem components and their interactions.
✨ Simple Example
Let’s consider an example of a home theater system. A home theater system consists of various components, such as a DVD player, a projector, a screen, and a sound system. To set up and control the home theater system, you have to control each component individually. However, with the Facade Pattern, we can create a simplified interface that hides the complexities of the individual components.
class DVDPlayer {
turnOn(): void {
console.log('DVD Player turned on');
}
playMovie(): void {
console.log('Movie playing');
}
turnOff(): void {
console.log('DVD Player turned off');
}
}
class Projector {
turnOn(): void {
console.log('Projector turned on');
}
setInput(input: string): void {
console.log(`Input set to ${input}`);
}
turnOff(): void {
console.log('Projector turned off');
}
}
class Screen {
up(): void {
console.log('Screen raised');
}
down(): void {
console.log('Screen lowered');
}
}
class SoundSystem {
turnOn(): void {
console.log('Sound System turned on');
}
setVolume(volume: number): void {
console.log(`Volume set to ${volume}`);
}
turnOff(): void {
console.log('Sound System turned off');
}
}
Now, you have HomeTheaterFacade
class acting as facade
that provides a simplified interface to interact with the home theater system.
class HomeTheaterFacade {
private dvdPlayer: DVDPlayer;
private projector: Projector;
private screen: Screen;
private soundSystem: SoundSystem;
constructor() {
this.dvdPlayer = new DVDPlayer();
this.projector = new Projector();
this.screen = new Screen();
this.soundSystem = new SoundSystem();
}
watchMovie(): void {
this.screen.down();
this.projector.turnOn();
this.projector.setInput('DVD');
this.dvdPlayer.turnOn();
this.dvdPlayer.playMovie();
this.soundSystem.turnOn();
this.soundSystem.setVolume(10);
}
endMovie(): void {
this.dvdPlayer.turnOff();
this.soundSystem.turnOff();
this.projector.turnOff();
this.screen.up();
}
}
The client code only needs to create an instance of the facade
and call the relevant methods, such as watchMovie()
and endMovie()
. The facade class internally manages the interactions with the individual components of the home theater system. Much easier, right?
// Client Code
const homeTheater = new HomeTheaterFacade();
homeTheater.watchMovie();
// Output:
// Screen lowered
// Projector turned on
// Input set to DVD
// DVD Player turned on
// Movie playing
// Sound System turned on
// Volume set to 10
homeTheater.endMovie();
// Output:
// DVD Player turned off
// Sound System turned off
// Projector turned off
// Screen raised
🧑🏻💻 Use it or Avoid it
When to use it:
- To provide a simple and unified interface to a complex subsystem, making it easier to use and understand.
- To hide the complexities and dependencies of individual subsystem components from the client code.
- To decouple the client code from the subsystem, promoting loose coupling and easier maintenance.
When to avoid it:
- If the underlying subsystem is simple and has only a few components. In such cases, the added abstraction may introduce unnecessary complexity.
- If you need fine-grained control over individual subsystem components.