[디자인 패턴] #12_Component Pattern, 컴포넌트 패턴
게임 디자인 패턴 중 "디커플링 패턴"에 대해 알아보겠습니다.
"게임 프로그래밍 패턴"의 14 항목, "컴포넌트"에 해당하는 내용입니다.
개념
1. 의도
한 객체가 여러 분야의 기능들을 "커플링" 없이 다룰 수 있도록 합니다. 복잡한 상속 관계를 통해 여러 기능들을 상속받아 사용하기보다, 각 기능 별 컴포넌트 클래스를 작성하고 해당 클래스 객체를 데이터 멤버로 두어 "디커플링"합니다.
패턴
1. 컴포넌트 패턴
한 객체에 필요한 다양한 분야의 기능들을 별도의 컴포넌트 클래스로 옮겨놓고, 이를 필요로 하는 객체는 단순히 "컴포넌트들의 컨테이너" 역할을 합니다!
2. 왜 필요한가?
class Player
{
public:
Player():velocity(0), x(0), y(0) {}
void Update(World& world, Graphics& graphics);
private:
static const int WALK_ACCELERATION = 1;
int velocity;
int x, y;
Volume volume;
//...
};
void Player::Update(World& world, Graphics& graphics)
{
// 입력 관련 코드들
switch (Controller::getJoystickDirection())
{
case DIR_LEFT:
velocity -= WALK_ACCELERATION;
break;
case: DIR_RIGHT:
velocity += WALK_ACCELERATION;
break;
//...
}
// 위치 관련 코드들
//...
// 렌더링 관련 코드들
//...
}
"Player" 클래스의 "Update()" 메서드는 매 프레임마다 호출되는 게임 루프입니다. "입력" 관련 코드, "물리" 관련 코드, 그리고 "렌더링" 관련 코드가 모두 들어가 있는 "Update()" 코드는 정리가 필요하죠! 이때, 각 기능 별로 개별적인 컴포넌트 클래스를 만들어 관련 코드들을 "Update()" 메서드로부터 옮겨놓음으로써, "커플링"을 제거합니다!
3. 언제 필요한가?
1. 한 클래스가 여러 분야와 커플링되어, "디커플링"이 필요할 때
2. 한 클래스가 너무 커져서 작업하기 힘들때
3. "상속"으로는 원하는 부분만 골라서 재사용할 수 없을 때
코드
1. 컴포넌트 패턴 적용
// 입력 컴포넌트, 인터페이스 클래스
class InputComponent
{
public:
virtual ~InputComponent();
virtual void Update(GameObject& gameObject) = 0;
};
// 물리 컴포넌트, 인터페이스 클래스
class PhysicsComponent
{
public:
virtual ~PhysicsComponent();
virtual void Update(GameObject& gameObject) = 0;
};
// 그래픽 컴포넌트, 인터페이스 클래스
class GraphicsComponent
{
public:
virtual ~GraphicsComponent();
virtual void Update(GameObject& gameObject) = 0;
};
class GameObject
{
public:
GameObject(InputComponent* _input, PhysicsComponent* _physics, GraphicsComponent* _graphics)
: input(_input), physics(_physics), graphics(_graphics)
{}
void Update(World& world, Graphics& currentGraphics)
{
input->Update(*this);
physics->Update(*this, world);
graphics->Update(*this, currentGraphics);
}
private:
InputComponent* input;
PhysicsComponent* physics;
GraphicsComponent* graphics;
};
1. 기능 별 Component 인터페이스 클래스 생성
GameObject 객체의 "Update()"에 들어있는 코드들을 기능 별로 나누어 각각의 Component 클래스를 생성합니다.
이때, 컴포넌트 클래스를 인터페이스 클래스로 생성하는 이유는 간단합니다. "추상화"(인터페이스와 구현의 분리)를 통해 "GameObject"로 생성할 다양한 객체들에 맞는 컴포넌트 클래스의 객체를 생성할 수 있기 때문입니다!
2. 필요한 컴포넌트 클래스를 가리키는 포인터를 데이터 멤버로 선언
개체에 필요한 기능들을 담은 컴포넌트 클래스들을 데이터 멤버로 두어 "디커플링"을 완성합니다.
고려 사항
1. 객체는 컴포넌트를 어떻게 얻어올까?
1. 객체가 필요한 컴포넌트를 직접 생성한다.
객체는 항상 필요한 컴포넌트를 갖고 있을 수 있습니다. 다만, 컴포넌트 패턴이 갖는 강점을, "플러그 앤 플레이", 잃게 됩니다. 따라서, 객체를 쉽게 변경하기 힘들죠.
2. 외부에서 컴포넌트를 제공할 때
객체가 훨씬 유연해집니다. 다양한 컴포넌트들을 연결하여 새로운 기능들을 추가하고, 혹은 삭제할 수 있기 때문이죠. 더불어, 객체에게 전달되는 컴포넌트 클래스는 구체 컴포넌트 클래스(컴포넌트 인터페이스를 상속받아 구체적인 구현을 수행한 클래스) 일 가능성이 높습니다. 컨테이너 객체는 인터페이스만 알고 있으므로, "캡슐화"에 유용합니다.
2, 컴포넌트 간 통신?
"컴포넌트 패턴"을 통해 컨테이너 클래스 내부에서 컴포넌트들은 서로 디커플링 상태를 유지하고 있습니다. 따라서, 컴포넌트들이 공유하는 정보는 모두 컨테이너 클래스 내부에 존재하죠. 더불어, 이들은 서로 통신하고 있다고 보기 어렵고, "Update()" 메서드 내부에서 호출되는 순서에 의존합니다. 그래서 우리는 컴포넌트 간 통신 방법에 대해서 알아보겠습니다.
1. 서로 직접 참조하는 방법
class NinjaGraphicsComponent { public: NinjaGraphicsComponent(NinjaPhysicsComponent* ninjaPhysics) :physics(ninjaPhysics) {} virtual void Update(GameObject& obj, Graphics& currentGraphics); private: NinjaPhysicsComponent* physics; //... };
컴포넌트의 생성자 인자로 다른 컴포넌트 객체를 넘겨줌으로써, 직접 통신하는 방법이 존재합니다. 간단하고, 빠른 방법이지만, 두 컴포넌트가 강하게 커플링 되겠죠
2. 메시지 전달// 모든 컴포넌트 클래스가 상속하는 Component 인터페이스 클래스 class Component { public: virtual ~Component(); // 순수 가상 메서드 virtual void receive(int message) = 0; }; class GameObject { public: //... void send(int message) { for (int i = 0; i < MAX_COMPONENTS; i++) { if (components[i] != NULL) { components[i]->receive(message); } } } private: static const int MAX_COMPONENTS = 3; vector<Components*> components; };
메시지를 통해 통신하는 방법 또한 존재합니다. 이를 통해 "Component" 클래스를 상속하는 모든 컴포넌트 클래스들은 메시지를 통해 통신이 가능하며, "디커플링" 상태를 유지할 수 있습니다! 다만, 컨테이너 클래스가 갖는 컴포넌트 개수가 늘어날수록 컴포넌트 배열이 커져 추가적인 수행 시간이 필요하다는 단점이 존재하죠
'언어 > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴]#14_서비스 중개자 패턴, Service Locator (0) | 2022.10.09 |
---|---|
[디자인 패턴]#13_이벤트 큐, Event Queue (0) | 2022.10.02 |
[디자인 패턴]#11_타입 객체, Type Object (0) | 2022.09.19 |
[디자인 패턴]#10_Sandbox Pattern, 샌드박스 패턴 (0) | 2022.09.12 |
[디자인 패턴]#9_업데이트 패턴, Update Pattern (0) | 2022.09.04 |