C++ Facade Pattern Complete Guide | One Simple Interface for a Complex Subsystem
이 글의 핵심
C++ Facade pattern hides a complex subsystem behind one simple interface: motivation, structure, multimedia and database examples, common mistakes, and production patterns.
What is the Facade pattern? Why use it?
The idea of exposing several subsystems through a single face is easier to compare with other patterns in the Structural Patterns series and the design patterns overview.
Problem scenario: a complex subsystem
Problem: To play video, the client must manipulate many classes directly.
// Bad design: the client must know every detail
int main() {
VideoFile video("movie.mp4");
CodecFactory factory;
Codec* codec = factory.extract(video);
AudioMixer mixer;
mixer.fix(video);
VideoConverter converter;
converter.convert(video, codec);
// Directly touching five classes...
}
Solution: The Facade pattern wraps a complex subsystem in one simple interface.
// Good design: Facade
// Type definitions
class VideoPlayer {
VideoFile video_;
CodecFactory factory_;
AudioMixer mixer_;
VideoConverter converter_;
public:
void play(const std::string& filename) {
video_.load(filename);
auto* codec = factory_.extract(video_);
mixer_.fix(video_);
converter_.convert(video_, codec);
// Hide internal complexity
}
};
int main() {
VideoPlayer player;
player.play("movie.mp4"); // Simple!
}
flowchart LR
client[Client]
facade[Facade
VideoPlayer]
subsystem1[Subsystem1
VideoFile]
subsystem2[Subsystem2
Codec]
subsystem3[Subsystem3
AudioMixer]
client --> facade
facade --> subsystem1
facade --> subsystem2
facade --> subsystem3
Table of contents
- Basic structure
- Multimedia system example
- Database wrapper
- Common problems and fixes
- Production patterns
- Full example: game engine initialization
1. Basic structure
#include <iostream>
#include <string>
// Subsystem classes (complex internals)
class Parser {
public:
void parse(const std::string& path) {
std::cout << "Parsing " << path << "...\n";
}
};
class Validator {
public:
bool validate() {
std::cout << "Validating...\n";
return true;
}
};
class Compiler {
public:
void compile() {
std::cout << "Compiling...\n";
}
};
class Linker {
public:
void link() {
std::cout << "Linking...\n";
}
};
// Facade: single entry point
class BuildPipeline {
Parser parser_;
Validator validator_;
Compiler compiler_;
Linker linker_;
public:
bool build(const std::string& path) {
std::cout << "=== Build Started ===\n";
parser_.parse(path);
if (!validator_.validate()) {
std::cout << "Validation failed!\n";
return false;
}
compiler_.compile();
linker_.link();
std::cout << "=== Build Complete ===\n";
return true;
}
};
int main() {
BuildPipeline pipeline;
if (pipeline.build("src/main.cpp"))
std::cout << "✓ Build OK\n";
return 0;
}
2. Multimedia system example
Video player Facade
#include <iostream>
#include <string>
#include <memory>
// Subsystem: complex video processing
class VideoFile {
std::string filename_;
public:
explicit VideoFile(std::string filename) : filename_(std::move(filename)) {
std::cout << "Loading video: " << filename_ << '\n';
}
std::string getCodecType() const { return "MPEG4"; }
};
class Codec {
public:
virtual ~Codec() = default;
virtual void decode() = 0;
};
class MPEG4Codec : public Codec {
public:
void decode() override { std::cout << "Decoding MPEG4...\n"; }
};
class H264Codec : public Codec {
public:
void decode() override { std::cout << "Decoding H264...\n"; }
};
class CodecFactory {
public:
static std::unique_ptr<Codec> extract(const VideoFile& file) {
if (file.getCodecType() == "MPEG4")
return std::make_unique<MPEG4Codec>();
return std::make_unique<H264Codec>();
}
};
class AudioMixer {
public:
void fix(const VideoFile& file) {
std::cout << "Fixing audio sync...\n";
}
};
class VideoRenderer {
public:
void render() {
std::cout << "Rendering video...\n";
}
};
// Facade: simple interface
class VideoPlayer {
public:
void play(const std::string& filename) {
std::cout << "=== Playing Video ===\n";
VideoFile video(filename);
auto codec = CodecFactory::extract(video);
codec->decode();
AudioMixer mixer;
mixer.fix(video);
VideoRenderer renderer;
renderer.render();
std::cout << "=== Playback Started ===\n";
}
};
int main() {
VideoPlayer player;
player.play("movie.mp4");
return 0;
}
Takeaway: The client only needs to call play().
3. Database wrapper
Simplifying complex DB operations
#include <iostream>
#include <string>
#include <vector>
// Subsystem: low-level DB operations
class Connection {
public:
void connect(const std::string& host) {
std::cout << "Connecting to " << host << "...\n";
}
void disconnect() {
std::cout << "Disconnecting...\n";
}
};
class Query {
public:
void prepare(const std::string& sql) {
std::cout << "Preparing query: " << sql << '\n';
}
void execute() {
std::cout << "Executing query...\n";
}
};
class ResultSet {
public:
std::vector<std::string> fetch() {
return {"row1", "row2", "row3"};
}
};
class Transaction {
public:
void begin() { std::cout << "BEGIN TRANSACTION\n"; }
void commit() { std::cout << "COMMIT\n"; }
void rollback() { std::cout << "ROLLBACK\n"; }
};
// Facade: simple DB interface
class Database {
Connection conn_;
Transaction trans_;
public:
void connect(const std::string& host) {
conn_.connect(host);
}
std::vector<std::string> query(const std::string& sql) {
trans_.begin();
try {
Query q;
q.prepare(sql);
q.execute();
ResultSet rs;
auto results = rs.fetch();
trans_.commit();
return results;
} catch (...) {
trans_.rollback();
throw;
}
}
void disconnect() {
conn_.disconnect();
}
};
int main() {
Database db;
db.connect("localhost:5432");
auto results = db.query("SELECT * FROM users");
for (const auto& row : results)
std::cout << "Row: " << row << '\n';
db.disconnect();
return 0;
}
Takeaway: Transactions, connections, query preparation, and execution are orchestrated behind a single query() call.
4. Common problems and fixes
Problem 1: Facade grows too large
// Bad example: God Object
class SystemFacade {
public:
void doEverything() { /* 100 lines */ }
void doMore() { /* 100 lines */ }
// 20 methods...
};
Fix: Split into multiple facades.
// Good example: separated responsibilities
class VideoFacade { /* video only */ };
class AudioFacade { /* audio only */ };
class NetworkFacade { /* network only */ };
Problem 2: Direct subsystem access
// Bad example: bypassing the Facade
VideoFile video("movie.mp4");
Codec* codec = new MPEG4Codec(); // direct access
Fix: Keep subsystems private or internal so only the Facade is used.
// Good example: hide subsystems
namespace internal {
class VideoFile { /* ... */ };
}
class VideoPlayer { // public API
internal::VideoFile video_;
};
Problem 3: Not every feature can be exposed
// Issue: Facade does not offer advanced options
player.play("movie.mp4"); // OK
player.setSubtitle("en"); // missing!
Fix: Add methods to the Facade for advanced scenarios, or expose controlled accessors to subsystems.
// Fix 1: add methods
class VideoPlayer {
public:
void play(const std::string& filename);
void setSubtitle(const std::string& lang); // added
};
// Fix 2: subsystem accessor
class VideoPlayer {
public:
VideoFile& getVideoFile() { return video_; } // for advanced users
};
5. Production patterns
Pattern 1: Singleton Facade
class Logger {
Logger() = default;
public:
static Logger& instance() {
static Logger inst;
return inst;
}
void log(const std::string& msg) {
// Hide a complex logging stack
std::cout << "[LOG] " << msg << '\n';
}
};
// Usage
Logger::instance().log("Application started");
Pattern 2: Combine with Builder
class VideoPlayerBuilder {
std::string codec_;
bool subtitles_ = false;
public:
VideoPlayerBuilder& setCodec(const std::string& c) { codec_ = c; return *this; }
VideoPlayerBuilder& enableSubtitles() { subtitles_ = true; return *this; }
VideoPlayer build() { return VideoPlayer(codec_, subtitles_); }
};
// Usage
auto player = VideoPlayerBuilder()
.setCodec("H264")
.enableSubtitles()
.build();
6. Full example: game engine initialization
#include <iostream>
#include <string>
// Subsystem: complex game engine components
class GraphicsEngine {
public:
void init() { std::cout << "Graphics: Initializing OpenGL...\n"; }
void setResolution(int w, int h) {
std::cout << "Graphics: Set resolution " << w << "x" << h << '\n';
}
void enableVSync() { std::cout << "Graphics: VSync enabled\n"; }
};
class AudioEngine {
public:
void init() { std::cout << "Audio: Initializing OpenAL...\n"; }
void setVolume(float v) {
std::cout << "Audio: Volume set to " << v << '\n';
}
};
class PhysicsEngine {
public:
void init() { std::cout << "Physics: Initializing Bullet...\n"; }
void setGravity(float g) {
std::cout << "Physics: Gravity set to " << g << '\n';
}
};
class InputManager {
public:
void init() { std::cout << "Input: Initializing SDL...\n"; }
void bindKey(const std::string& key, const std::string& action) {
std::cout << "Input: Bind " << key << " -> " << action << '\n';
}
};
class NetworkManager {
public:
void init() { std::cout << "Network: Initializing sockets...\n"; }
void connect(const std::string& server) {
std::cout << "Network: Connecting to " << server << "...\n";
}
};
// Facade: one interface for game engine startup
class GameEngine {
GraphicsEngine graphics_;
AudioEngine audio_;
PhysicsEngine physics_;
InputManager input_;
NetworkManager network_;
public:
void initialize(int width, int height) {
std::cout << "=== Game Engine Initialization ===\n";
graphics_.init();
graphics_.setResolution(width, height);
graphics_.enableVSync();
audio_.init();
audio_.setVolume(0.8f);
physics_.init();
physics_.setGravity(9.8f);
input_.init();
input_.bindKey("W", "MoveForward");
input_.bindKey("S", "MoveBackward");
network_.init();
std::cout << "=== Initialization Complete ===\n";
}
void connectToServer(const std::string& server) {
network_.connect(server);
}
void shutdown() {
std::cout << "=== Shutting Down ===\n";
}
};
int main() {
GameEngine engine;
engine.initialize(1920, 1080);
engine.connectToServer("game.server.com");
std::cout << "\n[Game Running...]\n\n";
engine.shutdown();
return 0;
}
Sample output:
=== Game Engine Initialization ===
Graphics: Initializing OpenGL...
Graphics: Set resolution 1920x1080
Graphics: VSync enabled
Audio: Initializing OpenAL...
Audio: Volume set to 0.8
Physics: Initializing Bullet...
Physics: Gravity set to 9.8
Input: Initializing SDL...
Input: Bind W -> MoveForward
Input: Bind S -> MoveBackward
Network: Initializing sockets...
=== Initialization Complete ===
Network: Connecting to game.server.com...
[Game Running...]
=== Shutting Down ===
Summary
| Topic | Description |
|---|---|
| Purpose | Provide a simple entry point to a subsystem |
| Pros | Simplifies callers, limits blast radius when subsystems change, hides complexity |
| Cons | May not expose every feature; Facade can become a bloated object |
| When to use | Wrapping complex libraries, multi-step initialization, legacy integration |
Related posts: Adapter, Decorator, Proxy, Bridge.
One-line summary: Use the Facade pattern to drive complex libraries, pipelines, or game engines through one small, stable interface.
Further reading (internal links)
Posts that connect well with this topic.
- C++ Adapter Pattern: Complete Guide | Interface conversion and compatibility
- C++ Decorator Pattern: Complete Guide | Dynamic feature composition
- C++ Proxy Pattern: Complete Guide | Access control and lazy loading
- C++ Bridge Pattern: Complete Guide | Separate abstraction and implementation for extensibility
Keywords (search terms)
Searches such as C++, Facade, design pattern, structural, API, simplification, and wrapper should surface content like this article.
Frequently asked questions (FAQ)
Q. When do I use this in production?
A. Whenever you need one place to start a library, legacy module, or cluster of classes: the Facade is a structural pattern that simplifies adoption; apply the examples and selection guidance above to your own APIs.
Q. What should I read first?
A. Follow Previous post / Related posts at the bottom of each article, or use the C++ series index for the full learning path.
Q. How do I go deeper?
A. Use cppreference and the official docs for the libraries you wrap. The reference links at the end of many posts are also useful.
Related posts
- C++ Bridge Pattern: Complete Guide | Separate abstraction and implementation for extensibility
- C++ Composite Pattern: Complete Guide | Tree structures with a uniform interface
- C++ Flyweight Pattern: Complete Guide | Share state to save memory
- C++ Adapter Pattern: Complete Guide | Interface conversion and compatibility
- C++ Decorator Pattern: Complete Guide | Dynamic feature composition