One-line Definition
Encapsulates a complex subsystem behind a single, simplified, higher-level interface.
Problem
Suppose your code needs to work with a complex library or framework. To complete a simple task, you must initialize dozens of objects, track dependencies, and execute methods in a precise, non-obvious order. This tightly couples your business logic to the third-party framework, making the code brittle and hard to read. A Facade pattern solves this by providing a simple, custom class that handles the complex choreography behind the scenes.
Real-world Analogy
When you start your car, you turn the key (or press a button). Behind the scenes, the ignition system, fuel pump, starter motor, battery, and ECU all coordinate in a specific sequence. You don't manually prime the fuel pump, then engage the starter, then time the spark plugs. The key is the facade — one simple action triggers the entire complex process.
Structure
- Client
- Interacts only with the Facade instead of the subsystem directly.
- Facade
- A single class with high-level methods that coordinate the subsystem classes in the correct sequence.
- Subsystem Classes
- The complex internal classes that do the actual work. They are completely unaware of the facade.
Diagram
classDiagram
class Client {
}
class Facade {
+simpleOperation()
}
class SubsystemA {
+complexOp1()
}
class SubsystemB {
+complexOp2()
}
class SubsystemC {
+complexOp3()
}
Client --> Facade : calls
Facade --> SubsystemA : orchestrates
Facade --> SubsystemB : orchestrates
Facade --> SubsystemC : orchestrates
Code Walkthrough
Notice that the Main class (the client) doesn't know about Amplifier, DVDPlayer, Projector, or Lights. It only interacts with the HomeTheaterFacade.
class Amplifier {
public void on() { System.out.println("Amplifier ON"); }
public void setVolume(int level) { System.out.println("Volume set to " + level); }
public void off() { System.out.println("Amplifier OFF"); }
}
class DVDPlayer {
public void on() { System.out.println("DVD Player ON"); }
public void play(String movie) { System.out.println("Playing: " + movie); }
public void off() { System.out.println("DVD Player OFF"); }
}
class Projector {
public void on() { System.out.println("Projector ON"); }
public void setWidescreen() { System.out.println("Projector in widescreen mode"); }
public void off() { System.out.println("Projector OFF"); }
}
class Lights {
public void dim(int level) { System.out.println("Lights dimmed to " + level + "%"); }
public void on() { System.out.println("Lights ON"); }
}
class HomeTheaterFacade {
private final Amplifier amp;
private final DVDPlayer dvd;
private final Projector projector;
private final Lights lights;
public HomeTheaterFacade(Amplifier amp, DVDPlayer dvd,
Projector projector, Lights lights) {
this.amp = amp;
this.dvd = dvd;
this.projector = projector;
this.lights = lights;
}
public void watchMovie(String movie) {
System.out.println("=== Getting ready to watch a movie ===");
lights.dim(10);
projector.on();
projector.setWidescreen();
amp.on();
amp.setVolume(7);
dvd.on();
dvd.play(movie);
}
public void endMovie() {
System.out.println("=== Shutting down ===");
dvd.off();
amp.off();
projector.off();
lights.on();
}
}
class Main {
public static void main(String[] args) {
HomeTheaterFacade theater = new HomeTheaterFacade(
new Amplifier(), new DVDPlayer(), new Projector(), new Lights()
);
theater.watchMovie("Inception");
System.out.println();
theater.endMovie();
}
}
Bad vs Good
Bad Approach
Problems
- Client must know about every subsystem class and the strictly required calling order.
- Orchestration logic is duplicated everywhere the client wants to "watch a movie."
- High coupling to low-level subsystems.
class Main {
public static void main(String[] args) {
Lights lights = new Lights();
Projector projector = new Projector();
Amplifier amp = new Amplifier();
DVDPlayer dvd = new DVDPlayer();
// Client orchestrates the heavy logic
lights.dim(10);
projector.on();
projector.setWidescreen();
amp.on();
amp.setVolume(7);
dvd.on();
dvd.play("Inception");
}
}
Better Approach
Improvements
- Client depends entirely on the abstraction provided by the Facade.
- Subsystem calling order and initialization is centralized, maintaining the Single Responsibility Principle for the client.
HomeTheaterFacade theater = new HomeTheaterFacade(
new Amplifier(), new DVDPlayer(), new Projector(), new Lights()
);
theater.watchMovie("Inception"); // Clean, abstract, intent-driven
Pros vs Cons
| Pros | Cons |
|---|---|
| Isolates clients from subsystem complexities | Can accidentally grow into a massive "God Object" |
| Provides a clean, intent-driven API | Doesn't strictly prevent clients from accessing subsystems directly |
| Centralizes orchestration logic | May hide advanced features of the underlying subsystem |
| Promotes loose coupling | |
| Eases the learning curve for integrating with complex libraries |
When to Use
- You need a limited, straightforward interface to a complex subsystem.
- You want to structure a subsystem into distinct layers.
- Client code is heavily coupled to the implementation details of a third-party framework.
When Not to Use
- The subsystem is already simple and easy to use.
- The client legitimately needs access to all the granular, low-level features of the subsystem.
Real-world Examples
javax.faces.context.FacesContext- Spring's various Template classes (e.g.,
JdbcTemplate,RestTemplate) which hide the boilerplate of raw connections and streams. - Libraries like Retrofit or Axios, which provide a facade over complex raw HTTP connections.
Key Takeaway
The Facade Pattern creates a simple, high-level API over a complex underlying system. Use it to hide boilerplate orchestration and minimize coupling to third-party libraries, but be careful not to let your Facade bloat into an unmaintainable God Object.