콘텐츠로 이동

C++ 구조 패턴 — Adapter·Decorator·Composite

구조 패턴(Structural Pattern)은 클래스와 객체를 더 큰 구조로 조합하는 방법을 다룹니다. 기존 코드를 수정하지 않고 기능을 확장하거나 인터페이스를 변환합니다.


호환되지 않는 인터페이스를 연결합니다. 서드파티 라이브러리나 레거시 코드를 현재 시스템에 연결할 때 사용합니다.

// 기존 물리 엔진 인터페이스 (변경 불가)
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});

객체에 동적으로 기능을 추가합니다. 상속 없이 조합으로 기능을 확장합니다.

// 기본 컴포넌트 인터페이스
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) = 33
auto enchanted = std::make_unique<PoisonEnchant>(
std::make_unique<FireEnchant>(std::move(weapon))
);
std::cout << enchanted->GetName() << "\n"; // Sword of Fire of Poison
std::cout << enchanted->GetDamage() << "\n"; // 33

개별 객체와 객체 집합을 동일하게 처리합니다. 트리 구조(씬 그래프, 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();

복잡한 서브시스템에 단순화된 인터페이스를 제공합니다.

// 복잡한 서브시스템
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");

객체에 대한 접근을 제어하는 대리자를 제공합니다. 지연 로딩, 캐싱, 접근 제어에 사용합니다.

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(); // 두 번째 호출: 로딩 없이 바로 Bind

패턴의도게임 활용
Adapter인터페이스 변환서드파티 물리/오디오 엔진 연결
Decorator동적 기능 추가무기 인챈트, 버프 스택
Composite트리 구조 균일 처리씬 그래프, UI 계층
Facade복잡성 은닉엔진 서브시스템 초기화
Proxy접근 제어지연 로딩, 캐싱

구조 패턴은 기존 코드를 수정하지 않고 새로운 기능을 조합할 수 있게 합니다. 특히 Decorator와 Composite는 게임 컴포넌트 시스템 설계에서 자주 만나는 패턴입니다.