C++ Facade Pattern | Simplify Complex Subsystems with a Single Interface
이 글의 핵심
C++ Facade Pattern. A structural pattern and practical example to wrap libraries, legacy code, or multiple classes into a single entry point for simplified usage.
What is the Facade Pattern?
Facade is a structural pattern that places a single interface in front of a complex subsystem (multiple classes or libraries). Clients can simply call the Facade without needing to understand the internal structure. Similar structural patterns include Adapter, Decorator, and Proxy, which are worth comparing.
Why use it?
- Simplification: Reduce complex APIs to a single entry point
- Decoupling: Hide subsystem details from clients
- Maintainability: Easier to refactor subsystems without affecting clients
- Convenience: Perform multiple steps in one go
// ❌ Without Facade: Complex usage
Parser parser;
parser.parse("file.cpp");
Validator validator;
if (!validator.validate()) {
return false;
}
Compiler compiler;
compiler.compile();
// ✅ With Facade: Simple usage
BuildPipeline pipeline;
pipeline.build("file.cpp");
How it works
The Facade pattern works by encapsulating subsystem complexity behind a unified interface. The Facade delegates calls to subsystem components and coordinates their interactions.
flowchart TD
Client["Client"]
Facade["Facade"]
SubA["Subsystem A"]
SubB["Subsystem B"]
SubC["Subsystem C"]
Client --> Facade
Facade --> SubA
Facade --> SubB
Facade --> SubC
Facade vs Similar Patterns
| Pattern | Purpose | Key Difference |
|---|---|---|
| Facade | Simplify complex subsystem | Provides a simplified interface |
| Adapter | Make incompatible interfaces work | Converts one interface to another |
| Decorator | Add functionality | Wraps objects to extend behavior |
| Proxy | Control access | Provides a surrogate for another object |
// Facade: Simplify
BuildPipeline pipeline;
pipeline.build("file.cpp");
// Adapter: Convert
LegacyLogger logger;
ModernLoggerAdapter adapter(logger);
adapter.log("message");
// Decorator: Extend
Logger* logger = new FileLogger();
logger = new TimestampDecorator(logger);
logger->log("message");
// Proxy: Control access
ImageProxy proxy("image.jpg");
proxy.display(); // Lazy loading
Basic Structure
#include <iostream>
#include <string>
// Subsystem classes (complex internals)
class Parser {
public:
void parse(const std::string& path) { /* Parsing */ }
};
class Validator {
public:
bool validate() { return true; }
};
class Compiler {
public:
void compile() { /* Compilation */ }
};
// Facade: Single entry point
class BuildPipeline {
Parser parser_;
Validator validator_;
Compiler compiler_;
public:
bool build(const std::string& path) {
parser_.parse(path);
if (!validator_.validate()) return false;
compiler_.compile();
return true;
}
};
int main() {
BuildPipeline pipeline;
if (pipeline.build("src/main.cpp"))
std::cout << "Build OK\n";
return 0;
}
Production Patterns
Pattern 1: Database Facade
#include <string>
#include <vector>
#include <memory>
// Subsystem: Connection Pool
class ConnectionPool {
public:
void* acquire() { return nullptr; }
void release(void* conn) {}
};
// Subsystem: Query Builder
class QueryBuilder {
public:
std::string buildSelect(const std::string& table) {
return "SELECT * FROM " + table;
}
};
// Subsystem: Result Parser
class ResultParser {
public:
std::vector<std::string> parse(void* result) {
return {"row1", "row2"};
}
};
// Facade: Simplified Database Access
class Database {
ConnectionPool pool_;
QueryBuilder builder_;
ResultParser parser_;
public:
std::vector<std::string> query(const std::string& table) {
auto conn = pool_.acquire();
auto sql = builder_.buildSelect(table);
// Execute query...
void* result = nullptr;
auto rows = parser_.parse(result);
pool_.release(conn);
return rows;
}
};
// Usage
Database db;
auto users = db.query("users");
Pattern 2: Multimedia Facade
#include <string>
// Subsystem: Video Decoder
class VideoDecoder {
public:
void decode(const std::string& file) {}
};
// Subsystem: Audio Decoder
class AudioDecoder {
public:
void decode(const std::string& file) {}
};
// Subsystem: Video Renderer
class VideoRenderer {
public:
void render() {}
};
// Facade: Media Player
class MediaPlayer {
VideoDecoder videoDecoder_;
AudioDecoder audioDecoder_;
VideoRenderer renderer_;
public:
void play(const std::string& file) {
videoDecoder_.decode(file);
audioDecoder_.decode(file);
renderer_.render();
}
void stop() {
// Stop all subsystems
}
};
// Usage
MediaPlayer player;
player.play("movie.mp4");
Pattern 3: HTTP Client Facade
#include <string>
#include <map>
// Subsystem: Connection Manager
class ConnectionManager {
public:
void connect(const std::string& url) {}
void disconnect() {}
};
// Subsystem: Request Builder
class RequestBuilder {
public:
std::string build(const std::string& method,
const std::string& path,
const std::map<std::string, std::string>& headers) {
return method + " " + path;
}
};
// Subsystem: Response Parser
class ResponseParser {
public:
std::string parse(const std::string& response) {
return response;
}
};
// Facade: HTTP Client
class HttpClient {
ConnectionManager connMgr_;
RequestBuilder reqBuilder_;
ResponseParser respParser_;
public:
std::string get(const std::string& url) {
connMgr_.connect(url);
auto request = reqBuilder_.build("GET", url, {});
// Send request...
std::string response = "HTTP/1.1 200 OK";
auto body = respParser_.parse(response);
connMgr_.disconnect();
return body;
}
std::string post(const std::string& url, const std::string& data) {
connMgr_.connect(url);
auto request = reqBuilder_.build("POST", url, {{"Content-Type", "application/json"}});
// Send request with data...
std::string response = "HTTP/1.1 201 Created";
auto body = respParser_.parse(response);
connMgr_.disconnect();
return body;
}
};
// Usage
HttpClient client;
auto response = client.get("https://api.example.com/users");
Common Issues
Issue 1: Over-simplification
Symptom: Facade hides too much functionality, requiring workarounds.
Solution: Expose additional methods or provide access to subsystems when needed.
// ❌ Over-simplified
class Database {
public:
std::vector<std::string> query(const std::string& table) {
// Only supports simple queries
}
};
// ✅ Balanced
class Database {
public:
// Simple interface
std::vector<std::string> query(const std::string& table) {}
// Advanced interface
std::vector<std::string> rawQuery(const std::string& sql) {}
// Access to subsystems
QueryBuilder& getQueryBuilder() { return builder_; }
private:
QueryBuilder builder_;
};
Issue 2: Tight Coupling
Symptom: Facade is tightly coupled to subsystems, making testing difficult.
Solution: Use dependency injection and interfaces.
// ❌ Tight coupling
class MediaPlayer {
VideoDecoder decoder_; // Concrete class
};
// ✅ Loose coupling
class IVideoDecoder {
public:
virtual ~IVideoDecoder() = default;
virtual void decode(const std::string& file) = 0;
};
class MediaPlayer {
std::unique_ptr<IVideoDecoder> decoder_;
public:
MediaPlayer(std::unique_ptr<IVideoDecoder> decoder)
: decoder_(std::move(decoder)) {}
};
// Usage
auto player = std::make_unique<MediaPlayer>(
std::make_unique<VideoDecoder>()
);
Issue 3: God Object
Symptom: Facade grows too large with too many responsibilities.
Solution: Split into multiple facades or use sub-facades.
// ❌ God Object
class Application {
public:
void initDatabase() {}
void initNetwork() {}
void initUI() {}
void initLogging() {}
void initCache() {}
// ... 50 more methods
};
// ✅ Multiple Facades
class DatabaseFacade {
public:
void init() {}
};
class NetworkFacade {
public:
void init() {}
};
class Application {
DatabaseFacade db_;
NetworkFacade net_;
public:
void init() {
db_.init();
net_.init();
}
};
Summary
| Item | Description |
|---|---|
| Purpose | Provides a simple entry point to a subsystem |
| Advantages | Simplifies usage, reduces impact when replacing or modifying subsystems |
| Disadvantages | May not expose all functionality (additional methods may be needed) |
FAQ
Q1: What is the Facade Pattern?
A: A structural pattern that provides a simplified interface to a complex subsystem.
BuildPipeline pipeline;
pipeline.build("file.cpp");
Q2: When should I use Facade?
A:
- Complex subsystems: Multiple classes with intricate interactions
- Legacy code: Wrap old code with a modern interface
- Third-party libraries: Simplify complex APIs
// Complex subsystem
Parser parser;
Validator validator;
Compiler compiler;
// Simplified Facade
BuildPipeline pipeline;
Q3: Facade vs Adapter?
A:
- Facade: Simplifies a complex subsystem
- Adapter: Converts one interface to another
// Facade: Simplify
BuildPipeline pipeline;
pipeline.build("file.cpp");
// Adapter: Convert
LegacyLogger logger;
ModernLoggerAdapter adapter(logger);
adapter.log("message");
Q4: Can Facade expose subsystems?
A: Yes. Provide access to subsystems for advanced use cases.
class Database {
public:
// Simple interface
std::vector<std::string> query(const std::string& table) {}
// Access to subsystems
QueryBuilder& getQueryBuilder() { return builder_; }
private:
QueryBuilder builder_;
};
Q5: How to avoid God Object?
A: Split into multiple facades or use sub-facades.
// Multiple Facades
class DatabaseFacade {
public:
void init() {}
};
class NetworkFacade {
public:
void init() {}
};
class Application {
DatabaseFacade db_;
NetworkFacade net_;
};
Q6: How to test Facade?
A: Use dependency injection and interfaces.
class IVideoDecoder {
public:
virtual ~IVideoDecoder() = default;
virtual void decode(const std::string& file) = 0;
};
class MediaPlayer {
std::unique_ptr<IVideoDecoder> decoder_;
public:
MediaPlayer(std::unique_ptr<IVideoDecoder> decoder)
: decoder_(std::move(decoder)) {}
};
Q7: Facade vs Proxy?
A:
- Facade: Simplifies multiple subsystems
- Proxy: Controls access to a single object
// Facade: Multiple subsystems
BuildPipeline pipeline;
pipeline.build("file.cpp");
// Proxy: Single object
ImageProxy proxy("image.jpg");
proxy.display();
Q8: Where can I learn more about Facade?
A:
- “Design Patterns” by Gang of Four
- “Head First Design Patterns” by Freeman & Freeman
- Refactoring.Guru - Facade
Related posts: Adapter Pattern, Decorator Pattern, Proxy Pattern.
One-line summary: The Facade Pattern allows you to use complex libraries or pipelines through a single, simplified interface.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
- C++ Decorator Pattern 완벽 가이드 | 기능 동적 추가와 조합
- C++ Proxy Pattern 완벽 가이드 | 접근 제어와 지연 로딩
이 글에서 다루는 키워드 (관련 검색어)
C++, Facade, design pattern, structural, API, simplification 등으로 검색하시면 이 글이 도움이 됩니다.