C++ Facade Pattern | Simplify Complex Subsystems with a Single Interface

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

PatternPurposeKey Difference
FacadeSimplify complex subsystemProvides a simplified interface
AdapterMake incompatible interfaces workConverts one interface to another
DecoratorAdd functionalityWraps objects to extend behavior
ProxyControl accessProvides 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

ItemDescription
PurposeProvides a simple entry point to a subsystem
AdvantagesSimplifies usage, reduces impact when replacing or modifying subsystems
DisadvantagesMay 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:

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 등으로 검색하시면 이 글이 도움이 됩니다.