C++ 구조 패턴 — Adapter·Decorator·Composite·Facade·Bridge·Flyweight
구조 패턴 개요
섹션 제목: “구조 패턴 개요”구조 패턴(Structural Pattern)은 클래스와 객체를 더 큰 구조로 조합하는 방법을 다룹니다. 기존 코드를 수정하지 않고 기능을 확장하거나 인터페이스를 변환합니다.
Adapter 패턴
섹션 제목: “Adapter 패턴”호환되지 않는 인터페이스를 연결합니다. 서드파티 라이브러리나 레거시 코드를 현재 시스템에 연결할 때 사용합니다.
// 기존 물리 엔진 인터페이스 (변경 불가)class PhysicsEngine{public: void ApplyForceXYZ(float fx, float fy, float fz) { std::cout << "Force: " << fx << ", " << fy << ", " << fz << "\n"; }};
// 게임에서 사용하는 벡터 타입struct Vec3 { float x, y, z; };
// 게임이 원하는 인터페이스class IPhysicsComponent{public: virtual void ApplyForce(Vec3 force) = 0; virtual ~IPhysicsComponent() = default;};
// 어댑터: PhysicsEngine을 IPhysicsComponent로 변환class PhysicsAdapter : public IPhysicsComponent{public: explicit PhysicsAdapter(PhysicsEngine& engine) : _engine(engine) {}
void ApplyForce(Vec3 force) override { _engine.ApplyForceXYZ(force.x, force.y, force.z); }
private: PhysicsEngine& _engine;};
// 사용: 게임 코드는 IPhysicsComponent만 알면 됨PhysicsEngine engine;PhysicsAdapter adapter(engine);IPhysicsComponent& physics = adapter;
physics.ApplyForce({0.0f, 9.8f, 0.0f});Decorator 패턴
섹션 제목: “Decorator 패턴”객체에 동적으로 기능을 추가합니다. 상속 없이 조합으로 기능을 확장합니다.
// 기본 컴포넌트 인터페이스class IWeapon{public: virtual int GetDamage() const = 0; virtual std::string GetName() const = 0; virtual ~IWeapon() = default;};
// 기본 무기class BasicSword : public IWeapon{public: int GetDamage() const override { return 10; } std::string GetName() const override { return "Sword"; }};
// 데코레이터 기반 클래스class WeaponDecorator : public IWeapon{public: explicit WeaponDecorator(std::unique_ptr<IWeapon> weapon) : _weapon(std::move(weapon)) {}
protected: std::unique_ptr<IWeapon> _weapon;};
// 불꽃 인챈트 데코레이터class FireEnchant : public WeaponDecorator{public: using WeaponDecorator::WeaponDecorator;
int GetDamage() const override { return _weapon->GetDamage() + 15; // 화염 피해 추가 }
std::string GetName() const override { return _weapon->GetName() + " of Fire"; }};
// 독 인챈트 데코레이터class PoisonEnchant : public WeaponDecorator{public: using WeaponDecorator::WeaponDecorator;
int GetDamage() const override { return _weapon->GetDamage() + 8; }
std::string GetName() const override { return _weapon->GetName() + " of Poison"; }};
// 데코레이터 체인auto weapon = std::make_unique<BasicSword>();// Sword(10) → Fire(+15) → Poison(+8) = 33auto enchanted = std::make_unique<PoisonEnchant>( std::make_unique<FireEnchant>(std::move(weapon)));
std::cout << enchanted->GetName() << "\n"; // Sword of Fire of Poisonstd::cout << enchanted->GetDamage() << "\n"; // 33Composite 패턴
섹션 제목: “Composite 패턴”개별 객체와 객체 집합을 동일하게 처리합니다. 트리 구조(씬 그래프, UI 계층)에 적합합니다.
// 컴포넌트 인터페이스class SceneNode{public: virtual void Render() const = 0; virtual void AddChild(std::shared_ptr<SceneNode>) {} virtual ~SceneNode() = default;
std::string name; explicit SceneNode(std::string n) : name(std::move(n)) {}};
// Leaf: 자식 없음class MeshNode : public SceneNode{public: using SceneNode::SceneNode;
void Render() const override { std::cout << " Render mesh: " << name << "\n"; }};
// Composite: 자식 보유class GroupNode : public SceneNode{public: using SceneNode::SceneNode;
void AddChild(std::shared_ptr<SceneNode> child) override { _children.push_back(std::move(child)); }
void Render() const override { std::cout << "Group: " << name << "\n"; for (const auto& child : _children) child->Render(); }
private: std::vector<std::shared_ptr<SceneNode>> _children;};
// 씬 그래프 구성auto root = std::make_shared<GroupNode>("Root");auto body = std::make_shared<GroupNode>("Character");auto weapon = std::make_shared<MeshNode>("Sword");auto terrain = std::make_shared<MeshNode>("Terrain");
body->AddChild(std::make_shared<MeshNode>("Torso"));body->AddChild(std::make_shared<MeshNode>("Head"));body->AddChild(weapon);
root->AddChild(body);root->AddChild(terrain);
// 루트에서 Render 호출 → 전체 트리 순회root->Render();Facade 패턴
섹션 제목: “Facade 패턴”복잡한 서브시스템에 단순화된 인터페이스를 제공합니다.
// 복잡한 서브시스템class AudioSystem{public: void Initialize() { std::cout << "Audio init\n"; } void LoadBank(const std::string& bank) { std::cout << "Load bank: " << bank << "\n"; } void PlayEvent(const std::string& event) { std::cout << "Play: " << event << "\n"; }};
class PhysicsSystem{public: void Initialize(float gravity) { std::cout << "Physics init, gravity=" << gravity << "\n"; } void SetCollisionGroups(int groups) { }};
class RenderSystem{public: void Initialize(int width, int height) { std::cout << "Render " << width << "x" << height << "\n"; } void SetShadowQuality(int quality) { }};
// 파사드: 엔진 초기화를 단일 메서드로class GameEngineFacade{public: void Initialize() { _audio.Initialize(); _audio.LoadBank("Master"); _physics.Initialize(-9.8f); _physics.SetCollisionGroups(8); _render.Initialize(1920, 1080); _render.SetShadowQuality(2); }
void PlaySound(const std::string& event) { _audio.PlayEvent(event); }
private: AudioSystem _audio; PhysicsSystem _physics; RenderSystem _render;};
// 클라이언트: 복잡성을 모름GameEngineFacade engine;engine.Initialize();engine.PlaySound("explosion");Proxy 패턴
섹션 제목: “Proxy 패턴”객체에 대한 접근을 제어하는 대리자를 제공합니다. 지연 로딩, 캐싱, 접근 제어에 사용합니다.
class ITexture{public: virtual void Bind() = 0; virtual ~ITexture() = default;};
class Texture : public ITexture{public: explicit Texture(const std::string& path) { std::cout << "Loading texture: " << path << "\n"; // 실제 로딩 로직 } void Bind() override { std::cout << "Bind texture\n"; }};
// 지연 로딩 프록시class LazyTextureProxy : public ITexture{public: explicit LazyTextureProxy(std::string path) : _path(std::move(path)) {}
void Bind() override { // 실제 Bind 호출 시 처음 한 번만 로드 if (!_texture) _texture = std::make_unique<Texture>(_path);
_texture->Bind(); }
private: std::string _path; std::unique_ptr<Texture> _texture; // 필요할 때까지 null};
// 사용: 생성 시 로딩 없음auto tex = std::make_unique<LazyTextureProxy>("large_texture.png");std::cout << "Proxy created (not loaded yet)\n";
// 처음 Bind 호출 시 로딩 발생tex->Bind(); // "Loading texture: large_texture.png" + "Bind texture"tex->Bind(); // 두 번째 호출: 로딩 없이 바로 BindBridge 패턴
섹션 제목: “Bridge 패턴”추상화(Abstraction)와 구현(Implementation)을 별도의 계층으로 분리해 독립적으로 확장할 수 있게 합니다. “상속 대신 구성”의 대표적 예입니다.
// 구현 계층 인터페이스 (플랫폼별 렌더러)class IRenderer{public: virtual ~IRenderer() = default; virtual void DrawRect(float x, float y, float w, float h) = 0; virtual void DrawText(const std::string& text, float x, float y) = 0;};
// 구체 구현: DirectXclass DirectXRenderer : public IRenderer{public: void DrawRect(float x, float y, float w, float h) override { std::cout << "DX DrawRect\n"; } void DrawText(const std::string& text, float x, float y) override { std::cout << "DX DrawText: " << text << "\n"; }};
// 구체 구현: OpenGLclass OpenGLRenderer : public IRenderer{public: void DrawRect(float x, float y, float w, float h) override { std::cout << "GL DrawRect\n"; } void DrawText(const std::string& text, float x, float y) override { std::cout << "GL DrawText: " << text << "\n"; }};
// 추상화 계층: UI 컴포넌트 (렌더러와 독립적으로 확장 가능)class UIWidget{protected: IRenderer& _renderer; // Bridge: 구현 계층 참조
public: explicit UIWidget(IRenderer& renderer) : _renderer(renderer) {} virtual ~UIWidget() = default; virtual void Draw() = 0;};
// 구체 추상화: 버튼 (렌더러 종류와 무관)class Button : public UIWidget{ float _x, _y, _w, _h; std::string _label;
public: Button(IRenderer& renderer, float x, float y, float w, float h, std::string label) : UIWidget(renderer), _x(x), _y(y), _w(w), _h(h), _label(std::move(label)) {}
void Draw() override { _renderer.DrawRect(_x, _y, _w, _h); _renderer.DrawText(_label, _x + 5, _y + 5); }};
// 장점: UI 클래스와 렌더러 클래스를 각각 독립적으로 추가 가능// 플랫폼 전환(DX->Vulkan)해도 UI 코드 수정 불필요DirectXRenderer dxRenderer;Button btn(dxRenderer, 100, 200, 80, 30, "Play");btn.Draw();Flyweight 패턴
섹션 제목: “Flyweight 패턴”수천 개의 유사한 객체에서 공유 가능한 불변 데이터를 분리해 메모리를 절약합니다.
// 공유되는 불변 데이터 (Intrinsic State)struct TreeType{ std::string meshPath; std::string texturePath; float scale; // 수천 그루의 나무가 이 데이터를 공유};
// 인스턴스별 가변 데이터 (Extrinsic State)struct TreeInstance{ const TreeType* type; // 공유 참조 (포인터만 보관) glm::vec3 position; // 인스턴스별 고유 위치 float rotation; // 인스턴스별 고유 회전 float health; // 인스턴스별 고유 체력};
// Flyweight Factoryclass TreeTypeFactory{ std::unordered_map<std::string, TreeType> _types;
public: const TreeType* GetOrCreate(const std::string& name, const std::string& mesh, const std::string& tex, float scale) { auto [it, inserted] = _types.try_emplace(name); if (inserted) it->second = { mesh, tex, scale }; return &it->second; }};
// 사용: 나무 10,000그루 생성// TreeType은 3~5개, TreeInstance는 10,000개TreeTypeFactory factory;const TreeType* oak = factory.GetOrCreate("oak", "oak.mesh", "oak.png", 1.0f);const TreeType* pine = factory.GetOrCreate("pine", "pine.mesh", "pine.png", 1.5f);
std::vector<TreeInstance> forest;for (int i = 0; i < 7000; ++i) forest.push_back({oak, {rand()%1000.f, 0, rand()%1000.f}, 0.f, 100.f});for (int i = 0; i < 3000; ++i) forest.push_back({pine, {rand()%1000.f, 0, rand()%1000.f}, 0.f, 100.f});
// TreeType 3개 × 수십 바이트 + TreeInstance 10000개 × 28바이트// vs 모든 데이터를 중복 저장: 10000개 × (수십 바이트 + 28바이트)// 메모리 절감: 수십 MB -> 수백 KB| 패턴 | 의도 | 게임 활용 |
|---|---|---|
| Adapter | 인터페이스 변환 | 서드파티 물리/오디오 엔진 연결 |
| Decorator | 동적 기능 추가 | 무기 인챈트, 버프 스택 |
| Composite | 트리 구조 균일 처리 | 씬 그래프, UI 계층 |
| Facade | 복잡성 은닉 | 엔진 서브시스템 초기화 |
| Proxy | 접근 제어 | 지연 로딩, 캐싱 |
| Bridge | 추상화·구현 분리 | 멀티플랫폼 렌더러, 크로스플랫폼 UI |
| Flyweight | 공유 데이터 최적화 | 나무·풀·파티클 등 대량 오브젝트 |
구조 패턴은 기존 코드를 수정하지 않고 새로운 기능을 조합할 수 있게 합니다. 특히 Flyweight는 오픈 월드 게임의 대량 환경 오브젝트(풀, 나무, 돌)에 필수적이며, Bridge는 멀티플랫폼 엔진에서 추상화 계층을 깔끔하게 유지하게 해줍니다.