[디자인 패턴] #3_관찰자 패턴, Observer Pattern
게임 디자인 패턴 중 "관찰자 패턴(Observer Pattern)"에 대해 알아보겠습니다.
"게임 프로그래밍 패턴"의 4 항목, "관찰자"에 해당하는 내용입니다.
관찰자 패턴, Observer
일대 다의 의존관계를 갖는 객체들이 어떠한 객체의 상태 변화를 그 객체와 의존성을 가진 다른 객체들이
그 변화를 "통지"받고 자동으로 업데이트할 수 있도록 합니다.
관찰자 패턴은 말 그대로 어떠한 객체의 상태 변화를 의존성을 갖는 다른 객체들에게, 혹은 관찰자들에게 그 변화를 통지하고 처리할 수 있게끔 하는 디자인 패턴입니다.
관찰자 패턴의 주요 포인트는 "피 관찰자"가 변화를 감지해 "관찰자"에게 그 변화들을 통지하는 객체들간의 관계 구조입니다.
말이 굉장히 복잡해 보입니다...
쉬운 이해를위해 아래 예제를 통해 관찰자 패턴을 자세히 알아보겠습니다.
업적 달성, Achievement
게임에서 "업적 달성"과 관련한 코드를 "관찰자 패턴"을 활용하여 작성해보겠습니다.
class Observer
{
public:
virtual ~Observer();
public:
/*
순수 가상 함수 onNotify : 특정 객체의 참조형과 Event를 인자로 받고,
"피 관찰자"로부터 "통보" 받은 정보를 처리합니다.
*/
virtual void onNotify(const Entity& entity, Event event) = 0;
};
먼저, "Observer(관찰자)" 추상 클래스를 정의합니다.
말 그대로 "관찰자" 클래스로서 "피 관찰자"로부터 통보받은 내용을 처리하도록, "onNotify()" 순수 가상 멤버 함수를 갖습니다.
class Achievement : public Observer
{
public:
virtual ~Achievement();
public:
virtual void onNotify(cons Entity& entity, Event event)
{
switch(event)
{
// Event 중 다리에서 추락을 성공했을 경우!
case EVENT_ENTITY_FELL :
// 다리에서 추락하는 업적을 해제합니다!
unlock(ACHIEVEMENT_FELL_OFf_BRIDGE);
break;
case EVENT_STEAL_MONEY :
unlock(ACHIEVEMENT_STEAL_MONEY);
break;
//... etc
}
}
private:
void unlock(Achievement _achievement);
//...
};
위 예제는 "Observer(관찰자)" 추상 클래스를 상속하는 "Acheivement(업적)" 클래스를 보여줍니다.
업적 클래스는 위 예제 코드처럼 "Observer(관찰자)" 클래스로부터 상속받은 "onNotify()" 순수 가상 함수를 정의하며,
인자로 받은 "Entity(대상)"과 "Event(이벤트)"의 종류를 통해 어떠한 업적을 해제할지 결정하고 있습니다!
따라서, 어떠한 종류의 클래스든 "Observer(관찰자)" 클래스를 상속하여 "onNotify()" 순수 가상 함수를 정의하게 되면, 관찰자 클래스가 될 수 있겠죠!
대상, Subject
class Subject
{
public:
// Observer 목록을 관리하는 배열에 Observer를 추가하거나 제거합니다.
void addObserver(Observer* _observer);
void removeObserver(Observer* )observer);
protected:
void notify(const Entity& entity, Event event)
{
// 모든 관찰자들에게 특정 사건과 그와 관련한 객체를 "통보"합니다.
for(int i=0; i< numOfObservers; ++i)
observers[i]->onNotify(entity, event);
}
private:
// Subject, 즉 관찰당하는 대상이 "관찰자"들의 목록을 데이터 멤버로 갖고있습니다.
Observer* observers[MAX_NUM_OBSERVERS];
int numOfObservers;
};
"관찰자 패턴"에서 중요한 점은 바로 "피 관찰자" 객체, 즉 "Subject(대상 or 피 관찰자)"입니다.
이들은 "관찰자" 목록을 멤버로 갖고 있으며, 자유롭게 외부에서 항목을 추가하거나 제거할 수 있습니다.
위 예제 코드들을 살펴보면 "Subject(대상 or 피 관찰자)" 클래스는 "Achievement(업적 or 관찰자)"에 관련된 내용은 담고 있지 않지만, 알림만 보내고 있습니다.
이 부분에서 우리는 "Subject(대상 or 피 관찰자)"는 "관찰자"와 상호작용하지만, 커플링 되어 있지 않다는 것을 알 수 있죠!
이것이 "관찰자 패턴"의 장점입니다!
관찰자 연결 리스트
"관찰자 패턴"의 단점은 과도한 "동적 할당"입니다.
위 코드 예제의 경우 간단한 배열을 사용했지만, 실제 게임 코드였다면 새로운 관찰자 추가와 삭제를 보다 편리하게 관리하기 위해 동적 할당 컬렉션을 사용합니다.
이때, 할당된 메모리를 회수하기 위해 상당한 시간이 필요해지겠죠!
그래서 우리는 동적 할당 없이 관찰자들을 등록하고 해제할 수 있는 "연결 리스트" 자료구조를 사용해보겠습니다.
class Subject
{
public:
Subject(): head(NULL){}
public:
// 인자로 받은 observer를 헤드로 설정하고, 앞쪽으로 추가합니다.
void AddObvserver(Observer* observer)
{
observer->next = head;
head = observer;
}
// 인자로 받은 observer가 head라면, observer->next를 head로 설정합니다.
void RemoveObserver(Observer* observer)
{
// 찾는 Observer가 첫 번째 노드일 경우
if(head == observer)
{
head = observer->next;
observer->next = NULL;
}else
{
Observer* current = head;
while(current != NULL)
{
// 1. Observer 노드를 찾았을 경우
if(current->next == observer)
{
current->next = observer->next;
observer->next = NULL;
}
// 2. 찾는 Observer가 아니라면 next로 이동합니다.
current = current->next;
}
}
}
void notify(const Entity& entity, Event event)
{
Observer* current = head;
while(current != NULL)
{
// 관찰자에 알림을 보내고 다음 Observer 노드로 이동합니다.
current->onNotify(entity, event);
current = current->next;
}
}
private:
Observer* head;
};
class Observer
{
friend class Subject;
public:
Observer(): next(NULL) {}
private:
Observer* next;
};
"연결 리스트" 자료구조를 활용한 "관찰자 패턴"을 예제 코드로 작성해봤습니다.
가장 먼저 눈여겨볼 점은 "Object" 추상 클래스 내부에 "Subject" 클래스를 "friend class"로 설정한 부분입니다.
간단한 배열을 사용했을 경우 "Subject(대상)" 객체가 직접 "Observer(관찰자)" 목록을 관리했다면,
연결 리스트 자료구조를 사용할 경우 "Observer(관찰자)" 클래스가 직접 "관찰자" 목록을 관리하기 때문입니다.
위 예제 코드는 "head"를 시작으로 새로운 "관찰자" 추가는 "head"의 뒤 쪽이 아니라 앞쪽으로 추가하고 있습니다.
단순히 tail 포인터 관리를 생략하기 위함입니다!
굳이 단점을 꼽자면, 맨 나중에 추가된 "관찰자"부터 알림을 받는 부작용이 존재하죠.
예를 들면, A, B, C 순서로 관찰자를 연결 리스트에 추가했다면, 알림은 C, B, A 순서로 받게 됩니다.
'언어 > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴]#6_상태 패턴, State Pattern (1) | 2022.08.20 |
---|---|
[디자인 패턴]#5_싱글턴 패턴, Singleton Pattern (0) | 2022.08.02 |
[디자인 패턴]#4_프로토타입 패턴, Prototype Pattern (0) | 2022.07.22 |
[디자인 패턴]#2_경량 패턴, Flyweight Pattern (0) | 2022.07.10 |
[디자인 패턴]#1_명령 패턴, Command Pattern (0) | 2022.07.07 |