[게임 프로그래밍 패턴] Chapter4 관찰자

Jangmanbo·2023년 8월 16일
0

관찰자 패턴
객체 사이에 1:n 의존 관계를 정의하여, 어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지 받고 자동으로 업데이트


1. 업적 달성

몬스터 100마리 죽이기, 다리에서 떨이지기 같은 업적들이 있는 업적 시스템을 개발할 때,
물리 엔진 한가운데 다리에서 떨어지면 업적을 해제하는 코드를 넣을 수는 없다.

물리 엔진은 추락할 때 이벤트를 날리면 업적 시스템이 이벤트를 받도록 하면 된다.


2. 작동 원리

관찰자: 관찰하는 객체 ex. 알림 시스템
대상: 관찰당하는 객체로 관찰자 목록을 가지고 있음 ex. 물리 엔진

대상 관찰자와 상호작용하지만, 커플링되어 있지는 않다.
또한 대상이 관찰자 '목록'을 갖고 있으므로 관찰자들은 각각 독립적이다.


3. 성능

관찰자 패턴은 정적 호출과 비교했을 때 문제가 될 만큼 느리지 않다.
단지 동기적으로 메서드들을 호출할 뿐이기 때문이다.


4. 동적 할당

관찰자를 추가, 삭제될 때마다 크기가 변하도록 동적 할당하는 게 꺼림칙하다면,
관찰자 연결 리스트로 동적 할당 없이 관찰자를 등록, 해제할 수 있다.

관찰자 연결 리스트

대상은 첫번째 관찰자 포인터를 가지고, 각 관찰자는 다음 관찰자 포인터를 가지게 한다.

이렇게 되면 대상이 관리해야 할 관찰자 목록을 관찰자가 갖고 있기 때문에,
대상이 관찰자 목록에 접근하기 위해서 Observer 클래스에서 Subject 클래스를 friend로 정의한다.

주의할 점은 관찰자 객체 그 자체가 연결 리스트의 노드이기 때문에, 관찰자는 하나의 대상의 관찰자 목록에만 등록 가능하다. (=한 대상만 관찰 가능)

리스트 노드 풀

관찰자 객체가 아니라 관찰자 포인터를 가지는 객체를 연결리스트의 노드로 한다.
또한 노드 객체가 같은 자료형이므로 미리 객체를 생성하여 동적 할당도 피한다. (= 오브젝트 풀링)

이러면 관찰자 연결 리스트의 한계점(한 대상만 관찰 가능)이 없으면서도 동적 할당 없이 관찰자를 등록할 수 있다.


5. 남은 문제점들

대상 제거

대상이 삭제되면 관찰자는 더이상 알림을 받을 수 없는데 계속 알림을 기다리게 된다.
해결: 대상이 제거되기 직전에 마지막으로 이벤트를 보낸다.

관찰자 제거

대상이 이미 삭제된 관찰자 객체를 가리키고 심지어 해당 객체에 이벤트를 보낼 수 있다.
이를 방지하기 위해선 관찰자가 삭제될 때 스스로를 등록 취소해야 한다.
아니면 관찰자가 제거될 때 자동으로 모든 대상으로부터 등록 취소하도록 만드는 방법도 있다. 단 이를 위해선 관찰자가 대상 목록을 관리해야 하므로 상호참조가 생긴다.

가비지 컬렉터

가비지 컬렉터가 있는데 명시적으로 관찰자 등록을 해제해야 하나?라는 의문이 생길 수 있다.

if 관찰자가 삭제될 때 명시적으로 관찰자 등록을 취소하지 않는다면

  • 일반적으로 UI를 닫으면 가비지 컬렉터가 UI 객체를 정리한다.
  • 그러나 대상에 UI 객체가 등록되어 있다면, 가비지 컬렉터가 UI 객체를 해제하지 않는다.
  • 해당 UI를 열 때마다 인스턴스가 생성되고 대상의 관찰자 목록에 추가되고 목록은 점점 커진다.
  • 대상은 이미 닫혀 보이지도 않는 UI 객체들에게 알림을 보내면서 CPU 클럭을 낭비한다.

이를 lapsed listener problem(사라진 리스너 문제)이라 부른다.

버그 수정의 어려움

관찰자 패턴의 목적은 두 코드간의 결합을 최소화하기 위함이다. (ex. 물리엔진에 업적시스템의 코드가 없음)

반대로 말하면, 버그가 여러 관찰자에 퍼져 있으면 흐름을 추론하기 어렵다. 어떤 관찰자가 알림을 받는지 런타임에서 확인해야 한다.

따라서 두 코드의 상호작용을 같이 확인해야 할 일이 많다면, 관찰자 패턴을 사용하지 않는 게 낫다.


오늘날의 관찰자

지금까지 배운 클래스를 이용한 관찰자 패턴은 사실 무겁고 융통성도 없다.
현재 일급 함수와 클로저를 지원하는 언어에서는 메서드나 함수 레퍼런스를 관찰자로 만드는 방법이 일반적이다.
ex. C# delegate, JavaScript EventListner


미래의 관찰자

대부분의 관찰자 패턴 코드들은 1. 어떤 상태가 변했다는 알림을 받음 2. 상태 변화를 UI에 반영하는 식이다.
데이터 바인딩은 어떤 값이 변경되면 관련된 UI 요소를 알아서 변경해주므로 관찰자 패턴 대신 사용하기에 좋다.
(단, 매우 느리므로 UI 같이 성능에 덜 민감한 분야 한정으로 사용해야 한다.)

0개의 댓글