C++ Facade 패턴 완벽 가이드 | 복잡한 서브시스템을 하나의 간단한 인터페이스로

C++ Facade 패턴 완벽 가이드 | 복잡한 서브시스템을 하나의 간단한 인터페이스로

이 글의 핵심

C++ Facade 패턴 완벽 가이드에 대한 실전 가이드입니다. 복잡한 서브시스템을 하나의 간단한 인터페이스로 등을 예제와 함께 상세히 설명합니다.

Facade 패턴이란? 왜 필요한가

여러 서브시스템을 한 얼굴로 묶는 흐름은 구조 패턴 시리즈종합 가이드에서 다른 패턴과 대비해 볼 수 있습니다.

문제 시나리오: 복잡한 서브시스템

문제: 비디오 재생을 위해 여러 클래스를 직접 조작해야 합니다.

// 나쁜 설계: 클라이언트가 모든 세부사항을 알아야 함
int main() {
    VideoFile video("movie.mp4");
    CodecFactory factory;
    Codec* codec = factory.extract(video);
    AudioMixer mixer;
    mixer.fix(video);
    VideoConverter converter;
    converter.convert(video, codec);
    // 5개 클래스를 직접 조작...
}

해결: Facade 패턴복잡한 서브시스템을 하나의 간단한 인터페이스로 감쌉니다.

// 좋은 설계: Facade
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);
        // 내부 복잡성 숨김
    }
};

int main() {
    VideoPlayer player;
    player.play("movie.mp4");  // 간단!
}
flowchart LR
    client["Client"]
    facade["Facadebr/(VideoPlayer)"]
    subsystem1["Subsystem1br/(VideoFile)"]
    subsystem2["Subsystem2br/(Codec)"]
    subsystem3["Subsystem3br/(AudioMixer)"]
    
    client --> facade
    facade --> subsystem1
    facade --> subsystem2
    facade --> subsystem3

목차

  1. 기본 구조
  2. 멀티미디어 시스템 예제
  3. 데이터베이스 래퍼
  4. 자주 발생하는 문제와 해결법
  5. 프로덕션 패턴
  6. 완전한 예제: 게임 엔진 초기화

1. 기본 구조

#include <iostream>
#include <string>

// 서브시스템 클래스들 (복잡한 내부)
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: 하나의 진입점
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. 멀티미디어 시스템 예제

비디오 플레이어 Facade

#include <iostream>
#include <string>
#include <memory>

// 서브시스템: 복잡한 비디오 처리
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: 간단한 인터페이스
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;
}

핵심: 클라이언트는 play() 하나만 호출하면 됩니다.


3. 데이터베이스 래퍼

복잡한 DB 연산을 단순화

#include <iostream>
#include <string>
#include <vector>

// 서브시스템: 저수준 DB 연산
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: 간단한 DB 인터페이스
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;
}

핵심: 트랜잭션, 연결, 쿼리 준비 등 복잡한 과정을 query() 하나로 처리합니다.


4. 자주 발생하는 문제와 해결법

문제 1: Facade가 너무 커짐

// ❌ 나쁜 예: God Object
class SystemFacade {
public:
    void doEverything() { /* 100줄 */ }
    void doMore() { /* 100줄 */ }
    // 20개 메서드...
};

해결: 여러 Facade로 분리하세요.

// ✅ 좋은 예: 책임 분리
class VideoFacade { /* 비디오 관련만 */ };
class AudioFacade { /* 오디오 관련만 */ };
class NetworkFacade { /* 네트워크 관련만 */ };

문제 2: 서브시스템 직접 접근

// ❌ 나쁜 예: Facade 우회
VideoFile video("movie.mp4");
Codec* codec = new MPEG4Codec();  // 직접 접근

해결: Facade만 사용하도록 서브시스템을 private/internal로 만드세요.

// ✅ 좋은 예: 서브시스템 숨김
namespace internal {
    class VideoFile { /* ... */ };
}

class VideoPlayer {  // public API
    internal::VideoFile video_;
};

문제 3: 모든 기능 노출 불가

// ❌ 문제: Facade가 고급 기능을 제공하지 않음
player.play("movie.mp4");  // OK
player.setSubtitle("en");  // 없음!

해결: 필요한 고급 기능은 Facade에 추가하거나, 서브시스템 접근자를 제공하세요.

// ✅ 해결 1: 메서드 추가
class VideoPlayer {
public:
    void play(const std::string& filename);
    void setSubtitle(const std::string& lang);  // 추가
};

// ✅ 해결 2: 서브시스템 접근자
class VideoPlayer {
public:
    VideoFile& getVideoFile() { return video_; }  // 고급 사용자용
};

5. 프로덕션 패턴

패턴 1: Singleton Facade

class Logger {
    Logger() = default;
public:
    static Logger& instance() {
        static Logger inst;
        return inst;
    }
    void log(const std::string& msg) {
        // 복잡한 로깅 시스템 감춤
        std::cout << "[LOG] " << msg << '\n';
    }
};

// 사용
Logger::instance().log("Application started");

패턴 2: 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_); }
};

// 사용
auto player = VideoPlayerBuilder()
    .setCodec("H264")
    .enableSubtitles()
    .build();

6. 완전한 예제: 게임 엔진 초기화

#include <iostream>
#include <string>

// 서브시스템: 복잡한 게임 엔진 컴포넌트
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: 게임 엔진 초기화를 하나의 인터페이스로
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;
}

출력:

=== 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 ===

정리

항목설명
목적서브시스템에 대한 단순한 진입점 제공
장점사용처 단순화, 서브시스템 교체·수정 시 영향 범위 축소, 복잡성 숨김
단점모든 기능을 노출하지는 못함, Facade가 비대해질 수 있음
사용 시기복잡한 라이브러리 래핑, 멀티 스텝 초기화, 레거시 코드 감싸기

관련 글: Adapter 패턴, Decorator 패턴, Proxy 패턴, Bridge 패턴.

한 줄 요약: Facade 패턴으로 복잡한 라이브러리·파이프라인·게임 엔진을 하나의 간단한 인터페이스로 쓸 수 있습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ Decorator Pattern 완벽 가이드 | 기능 동적 추가와 조합
  • C++ Proxy Pattern 완벽 가이드 | 접근 제어와 지연 로딩
  • C++ Bridge 패턴 완벽 가이드 | 구현과 추상화 분리로 확장성 높이기

이 글에서 다루는 키워드 (관련 검색어)

C++, Facade, design pattern, structural, API, simplification, wrapper 등으로 검색하시면 이 글이 도움이 됩니다.

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++ Facade 패턴 완벽 가이드. 라이브러리·레거시·여러 클래스를 하나의 진입점으로 감싸 사용을 단순화하는 구조 패턴, 실전 예제, 멀티미디어 시스템, 데이터베이스 래퍼까지. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


관련 글

  • C++ Bridge 패턴 완벽 가이드 | 구현과 추상화 분리로 확장성 높이기
  • C++ Composite 패턴 완벽 가이드 | 트리 구조를 동일 인터페이스로 다루기
  • C++ Flyweight 패턴 완벽 가이드 | 공유로 메모리 절약하기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ Decorator Pattern 완벽 가이드 | 기능 동적 추가와 조합