C++ Facade Pattern Complete Guide | One Simple Interface for a Complex Subsystem

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

  1. Basic structure
  2. Multimedia system example
  3. Database wrapper
  4. Common problems and fixes
  5. Production patterns
  6. 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

TopicDescription
PurposeProvide a simple entry point to a subsystem
ProsSimplifies callers, limits blast radius when subsystems change, hides complexity
ConsMay not expose every feature; Facade can become a bloated object
When to useWrapping 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.


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.


  • 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