Facade Pattern
- Description: Provide a single, simplified interface to a complicated subsystem of classes — clients depend on the facade, not the subsystem's internals.
- My Notion Note ID: K2C-2-10
- Created: 2026-05-22
- Updated: 2026-05-22
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. Intent
- 2. Structure
- 3. C++ Example
- 4. When to Use
- 5. Variants and Pitfalls
- 6. Related Patterns
- 7. References
1. Intent
- Subsystem with many cooperating classes → clients drown in setup, ordering, lifetimes.
- Facade exposes one coherent API for the common 80% use case. Power users still reach into the subsystem directly.
- Decouples client from subsystem internals → swap implementation, refactor internals, no client breakage.
Goal is simplification, not isolation. Facade is not a wall — clients can bypass it.
Canonical examples → ffmpeg lib wrappers, OpenGL framework "scene" classes, HTTP client SDKs that hide socket / DNS / TLS / parsing.
2. Structure
| Role | Responsibility |
|---|---|
| Facade | Single entry point. Translates high-level calls into orchestrated subsystem calls. |
| Subsystem classes | Do the real work. Unaware of the Facade. |
| Client | Talks to the Facade (default) or directly to subsystem classes (advanced). |
No abstract base needed. Facade is usually a concrete class with a small, task-oriented API.
3. C++ Example
3.1 Video conversion facade
#include <string>
#include <iostream>
#include <utility>
// Subsystem — verbose, granular
class VideoFile { public: explicit VideoFile(std::string p) : path_(std::move(p)) {} std::string path() const { return path_; } private: std::string path_; };
class CodecFactory { public: std::string pick(const VideoFile&) { return "mpeg4"; } };
class BitrateReader { public: std::string read(const VideoFile&, const std::string&) { return "<bytes>"; } std::string convert(const std::string& buf, const std::string&) { return buf + "/converted"; } };
class AudioMixer { public: std::string fix(const std::string& buf) { return buf + "+audio"; } };
// Facade — single call wrapping the whole pipeline
class VideoConverter {
public:
std::string convert(const std::string& path, const std::string& target) {
VideoFile file{path};
auto src_codec = CodecFactory{}.pick(file);
BitrateReader reader;
auto buf = reader.read(file, src_codec);
auto converted = reader.convert(buf, target);
return AudioMixer{}.fix(converted);
}
};
int main() {
VideoConverter conv;
std::cout << conv.convert("input.mp4", "ogg") << "\n";
}
- Client sees one method. Internals can swap codec libs, add caching, change order.
- Power-user code still constructs
BitrateReaderdirectly if needed.
3.2 Library facade over a C API
// Subsystem: bare C handle-based API (sqlite3, libcurl, ffmpeg)
// Facade: RAII C++ wrapper exposing only a few high-level operations.
class Db {
public:
explicit Db(const std::string& path); // calls sqlite3_open, checks, throws
~Db(); // sqlite3_close
Db(const Db&) = delete;
Db& operator=(const Db&) = delete;
Db(Db&&) noexcept;
Db& operator=(Db&&) noexcept;
std::vector<Row> query(const std::string& sql);
void exec(const std::string& sql);
private:
struct sqlite3* h_; // raw handle hidden
};
- Hides handle management, error code translation, statement prep/finalize.
- Common case:
db.query("SELECT ..."). Power user can still geth_via a friend / accessor for esoteric calls.
4. When to Use
Use when:
- Subsystem has high learning curve / many classes / fragile init order.
- Most clients only need a small slice of functionality.
- Want a stable API in front of churn-prone internals.
- Layering — Facade at each layer boundary keeps layers loosely coupled.
Avoid when:
- Subsystem is already simple → Facade adds a useless indirection.
- Facade keeps growing endpoints → it's becoming a god-object; split.
- Need full subsystem flexibility → don't force everyone through a narrow door.
5. Variants and Pitfalls
- Multiple facades for the same subsystem, each targeting a use case (read-only client, admin client). Better than one fat facade.
- Facade + Singleton. Convenient but couples global state; testing pain. Prefer injecting the facade.
- Abstract Facade. Pure-virtual facade base + concrete implementations → swappable backends. Crosses into Strategy / Bridge territory.
- Facade as service entry. Microservice public API is a Facade over internal services.
Pitfalls:
- God-facade. One class exposing 50 methods spanning unrelated concerns → split by use case.
- Leaky facade. Returns subsystem types (raw handles, internal enums) → clients couple to internals anyway.
- Facade-only access. If clients are forbidden to touch the subsystem, that's closer to Mediator (centralized control). Facade allows bypass; Mediator does not.
- Hidden cost. A simple facade call may do expensive setup → document complexity, don't surprise the caller.
- Versioning. Once published, breaking the facade breaks every caller. Add new methods; deprecate, don't remove.
6. Related Patterns
- Facade vs Adapter. Adapter converts one interface to another expected interface — 1-to-1 shape translation. Facade defines a new interface over a subsystem — 1-to-many. Adapter solves a fit problem; Facade solves a complexity problem.
- Facade vs Mediator. Both centralize. Mediator owns the interaction protocol — colleagues talk only through the mediator, never directly. Facade is a convenience — subsystem classes still talk to each other directly; clients optionally route through the facade. Mediator restricts; Facade simplifies.
- Facade vs Decorator. Decorator preserves the wrapped interface and adds behavior. Facade introduces a new, smaller interface over many classes.
- Facade vs Proxy. Proxy keeps the subject's interface and controls access. Facade defines a new, smaller interface over many subjects.
- Facade + Abstract Factory. Facade often hands clients pre-configured subsystem objects → the Facade's constructor or factory method plays Abstract Factory.
- Singleton. Facades are often singletons, but that's a separate decision; nothing in Facade requires global state.