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. 기본 구조
#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 완벽 가이드 | 기능 동적 추가와 조합