오르카의 아틀리에


오랜만에 다시 디자인 패턴을 포스팅하는 것 같다. 마찬가지로 C++가 아닌 C#이나 Javascript, Python을 이용하여 프로그래밍하다가 C++을 하려니 간단한 예제인데도 포인터로 실수 좀 했다 ㅎㅎ... 이번 패턴은 옵저버 패턴이다. 직역하면 관찰자, 감시자 정도일 것 같은데 이름만 들어서는 감시하는 객체가 빡시게 일할 것 같지만 사실 감시당하는 객체 쪽에서 열심히 일해주어야 한다. (처음 배울 때 관찰당하는 객체와 하는 객체의 역할이 헷갈려서 고생 좀 했었지...) 기본적인 메커니즘은 '관찰하고 싶은 객체'가 '관찰당하고 싶은 객체'에게 "나 널 관찰하고 싶어!"라고 알려주면, '관찰 당하고 싶은 객체'가 기억해 두었다가 특정 사건이나 변화가 일어날 때 '관찰하고 싶은 객체'에게 찾아가 알려주는 방식이다. 그림으로 보자면 다음과 같다고 할 수 있다.



이렇게 관찰당하는 사람이 자발적으로 알려준다는 점에서 우리가 일반적으로 알고 있는 '관찰자'의 이미지와는 조금 다르다고 할 수 있다. 여튼 이 패턴은 게임개발에서도 아주 자주 사용하는 패턴 중의 하나이다. 업적 시스템이나 캐릭터 체력이나 상태 관련 UI 업데이트 등에서 사용하고 있고, 익히알고있는 MVC 패턴에서 M과 V를 느슨하게 연결하게 해주는 패턴이기도 하다. 그렇다면 어떤 구조로 이루어져 있을까? UML을 통해 살펴보자.


UML


글로 간단하게 설명하자면, Observable은 관찰당할 클래스가 상속받는 클래스이다. ObserverList를 이용해 자신을 관찰하는 옵저버들을 가지고 있고, Observer는 AddObserver()를 이용하여 ObserverList에 스스로 등록하게 된다. 그리하여 ConcreteObservable의 여러 Method를 통해 특정 상태가 바뀌거나 이벤트가 발생했을 때 ObserverList에 있는 Observer들의 Notify() 메소드를 호출하여 알려 줄 수 있게 되는 것이다. 심플하게 작동한다. 물론 도전과제처럼 원하는 작업이 끝나면 RemoveObserver()를 이용하여 더이상 관찰하지 않아도 된다. 글로 대강 이해가 되었으면 소스코드를 살펴보자.


소스코드



첫 번째 소스코드를 보면 Observer와 Observable에 대한 함수들이 정의되어있고 함수들은 가상 함수로 선언되어있다. 일단 ObserverList는 사용하기 무난한 STL Vector를 이용하였고, Observer는 간단하게 Notify만 정의되어있다. 위 클래스를 상속받은 ConcreteClass를 살펴보자.



두 번째 소스코드는 이해를 조금이나마 더 쉽게 하기 위해 '유저의 상태'와 '게임 오버 판넬'로 예시를 들었다. 

UserModel은 상속받은 클래스의 가상함수를 구현하고 있는 것을 볼 수 있고. vector에 Observer들을 저장하고 지우는 코드이니 이 부분은 간단할 것이다. AttackByEnemy()를 통해 공격받은 공격력과 관통력, 방어력을 이용하여 남은 HP를 계산하고 HP가 0이 되면 이 이벤트를 GameOverView를 Notify()를 통해 전달한다. 


사실 이 방법을 사용하면 한번에 여러 가지 정보나 이벤트를 전달하긴 힘들 것이다. 그래서 특별한 상황에서는 다른 방법을 사용할 수 있는데 다음과 같다.



보면 바로 알 수 있겠지만, Notify의 파라메터에 Observable을 넘겨서 원하는 상태 변수에 접근할 수 있도록 도와줄 수도 있다. (이 경우에는 상태 변수에 접근할 수 있는 메소드들을 만들어 주어야겠지만...)


왜 사용할까?

답은 간단하다. 눈치가 빠르다면 코드를 읽을 때 눈치를 챘을 수도 있다. 상태 변화를 수신해야 하는 객체가 여러 개거나 전달 받아야 할 정보가 많을수록 객체들이 많아질 수 있고, 그렇게 되면 객체들의 연결이 복잡해지고 강하게 연결될 가능성이 크다. 때문에 옵저버 패턴을 이용하여 최대한 느슨하게 연결할 수 있도록 도와주기 때문이다.