C++ 데코레이터 패턴
객체들은 구현할 때 어떤 기능을 가질 것인지 미리 정해져 있습니다. 그런데 개발이 진행되면서 혹은 상황에 따라 특정 객체에 동적으로 기능을 추가하거나 삭제해야 할 일도 있습니다. 1942 같은 비행기 슈팅 게임을 보면 아이템을 획득함에 따라 처음엔 앞쪽에만 공격이 나가다가 여러 방향으로 나가는 기능이 추가됩니다.
간단히 구현해보면 공격 방향에 대한 변수를 가지고 있으면서 아이템이 획득함에 따라 그 변수에 값을 변경하여 공격 시 변수에 담겨있는 값에 따른 공격을 하게 하면 됩니다.
const int FRONT 1
const int SIDE 2
class Item {
Item(int dir) { direction = dir; }
int GetDirection() { return direction; }
private:
int direction;
};
class Airplane {
void AddItem(Item* item) { direction += item->GetDirection(); }
void Attack() {
if ( direction & SIDE ) { cout << "side Attack"; } // side 아이템 가지고 있으면
cout << "front Attack"; // 기본 공격
}
private:
int direction;
};
int main()
{
Airplane plane;
plane.attack();
Item side(SIDE);
plane.AddItem(&side);
plane.attack();
}
위와 같이 구현하면 특정 객체에만 기능을 추가해주는 방향이 아닌 Airplane 모든 객체가 해당 기능을 가지기 때문에 의도와는 다르게 됩니다. 그리고 기존에 정해져 있는 아이템이 아닌 뒤로 발사되는 아이템이 추가 개발된다면 기존 클래스를 수정해야 한다는 번거로움이 있습니다.
하지만 클래스를 변경시키지 않고는 새로운 기능을 추가하기는 어려우니 Airplane 객체가 아닌 다른 객체를 이용해서 원래 객체를 꾸며주는 식으로 하면 됩니다. 새로운 기능을 가진 객체가 원래 객체를 꾸며서 새로운 기능을 수행하고 원래 객체도 가지고 있던 기능을 수행하면 새로운 기능이 추가, 삭제된 것처럼 보입니다.
기본 Airplane은 전방 공격만 하고 있으므로 FrontAttackAirplane 클래스가 되고 좌우측 공격 아이템을 먹었을 때 SideAttackAirPlane는 FrontAttackAirPalne을 참조하게 됩니다. 이런식으로 상위 기능을 가지는 클래스에서 하위 클래스를 참조하는 구조를 가지면 됩니다. 그리고 사용자 입장에서는 동일한 인터페이스를 통해 사용이 가능해야 합니다.
class AirPlane { virtual void Attack() = 0; }
class FrontAttackAirPlane : public AirPlane {
void Attack() { cout << "Front Attack"; }
};
class Decorator : public AirPlane {
Decorator( AirPlane* pAir ) { pComponent = pAir; }
virtual void Attack() {
if ( pComponent != 0 ) { pComponent->Attack(); }
}
private:
AirPlane* pComponent;
};
class SideAttackAirPlane : public Decorator {
SideAttackAirPlane(AirPlane* pAir) : Decorator(pAir) {}
void Attack() {
Decorator::Attack();
cout << "Side Attack";
}
};
class RearAttackAirPlane : public Decorator {
RearAttackAirPlane(AirPlane* pAir) : Decorator(pAir) {}
void Attack() {
Decorator::Attack();
cout << "Rear Attack";
}
};
int main()
{
AirPlane* pFront = new FrontAttackAirPlane; // 기본 공격
AirPlane* pSide = new SideAttackAirPlane(pFront);
AirPlane* pRear = new RearAttackAirPlane(pFront);
pRear->Attack();
}
위와 같이 구현하면 pSide나 pRear는 기본 공격을 하는 pFront를 인자로 받아 FrontAttackAirPlane의 Attack을 수행할 뿐 아니라 자신의 Attack도 수행하게 됩니다.
데코레이터 패턴에서는 데코레이터 클래스가 상속받는 최상위 클래스는 최대한 간단히 구현하는 것이 좋습니다. 데이터 멤버는 정의하지 않고 인터페이스는 최소로 하는 것이 기능을 추가 삭제할 때 최상위 클래스에 여러 요소가 많다면 객체 생성 시 메모리를 불필요하게 많이 가져갈 수 있기 때문입니다.
지금까지 보여드린것처럼 데코레이터 패턴은 기본 객체를 건드리지 않고 기능을 추가 혹은 삭제시킬 수 있는 방법입니다. 새로운 기능을 가지는 객체들은 기본 객체를 가지고와 객체 간에 참조 연결 고리를 형성하여 이를 따라가면서 객체가 수행해야 할 기능들을 수행하고 있습니다.