디자인 패턴 - 5. 옵저버 패턴

땡구의 개발일지·2025년 5월 11일
post-thumbnail

C#에서 델리게이트, 이벤트 / 유니티에서는 이벤트

Update()마다 값이 변화 되었는지 체크 하는 것은 비효율 적이다. 값이 변할 때만, 업데이트를 받는 콜백으로 구성하는 것을 옵저버 패턴이라고 한다. 기존의 델리게이트 제네릭, 이벤트, 그리고 유니티 이벤트로 구현되어 있고 사용해왔다

옵저버 패턴

  • 주시 대상(Subject)이 되는 객체가 자신의 데이터 변경 시, 등록된 관찰자(Observer)들에게 알려주는 디자인 패턴.
  • 주기적으로 확인하지 않아도 데이터 변화에 대응할 수 있으므로 게임 최적화의 의도와 잘 부합하는 패턴.
  • 주시대상상태 변화가 있을 때 관찰자들을 호출하는 방식이 아닌, 관찰자들이 주시대상의 상태 변화에 반응하는 방법을 구현하는 방법 = 콜백

Subject

  • 주시대상. 데이터와 옵저버들을 가지고 있는 주체. 데이터 변경시 등록된 여러 옵저버들에게 메서드를 통해 메시지를 전달.

Observer

  • Subject를 주시하고 있는 관찰자. 데이터 변경에 대한 메시지 수신 시, 자신이 해야 할 동작을 수행.

주의점

1. 구독 해지

  • 옵저버 패턴은 구독자를 주체에 등록하는 방식. 추후 사용되지 않을 객체를 구독 해지한다. 그렇지 않을 경우, 불필요한 메모리 공간을 차지한다

예시) 게임 일시정지

  • 게임 매니저
public event Action OnPaused;

public static void Pause()
{
	Time.timeScale = 0f;
    OnPaused?.Invoke();
}
  • 이벤트 구독. Tank 스크립트

private void OnEnable()
// private void Start()
{
	Manager.Game.OnPaused += 일시 정지 시 수행할 기능;
}
  • 이벤트 구독해제. Tank 스크립트
private void OnDisable()
//private void OnDestroy()
{
	Manager.Game.OnPaused -= 일시 정지 시 수행할 기능;
}

2. 옵저버 중계자(중재자)

  • 구독자 수가 많아질 수록 순회하는 숫자가 늘어나니 무거워진다. 이럴때 중계자를 사용해 문제를 해결할 수 있다
  • 몬스터가 800마리 정도가 있다 그러면 게임 매니저에서 삭제시키기에는 부담이 크다. 중계자를 만들어서 게임매니저 에서는 삭제 명령만 하달받고, 해당 수행은 중계자가 코루틴으로 구현한다. 그래서 업데이트마다 순회할 일이 없어서 가벼워진다

참고


3. 구조 복잡도 증가

  • 구독자와 주시대상이 많아질 수록, 직관성이 떨어진다. 코드를 알아보기 힘들어 질 수 있으니, 설계 단계부터 조심해야한다
  • 이벤트 체인이 안되게 조심해야된다. (이벤트가 이벤트를 수행하는 구조)
  • 이벤트를 구독하는 함수에서 다른 이벤트를 포함시키지 않게 하자

구현

  1. 주시대상에 관찰자들의 목록과 관찰자의 등록, 해제를 구현
  2. 관찰자는 주시대상의 상태 변화를 확인하고 싶은 경우, 목록에 등록
  3. 주시대상에 상태 변화가 있는 경우 목록의 관찰자들을 순회하며 알림
  4. 관찰자의 수가 많을 경우, 중재자 패턴을 사용한다

장점

  1. 클래스간의 느슨한 연결구조로 유연한객체 지향 시스템을 구축
  2. 개방폐쇄원칙을 준수하게 됨

주의점

  1. 알림이 가는 순서를 보장하지는 않음
  2. 이벤트 체인, 순환 구조 등의 문제

예시

public class Observer
{
    public void Notify() { }
}

public class Subject
{
    private List<Observer> observerList = new List<Observer>();

    public void RegisterObserver(Observer observer)
    {
        observerList.Add(observer);
    }

    public void UnregisterObserver(Observer observer)
    {
        observerList.Remove(observer);
    }

    private void NotifyObserver()
    {
        foreach (Observer observer in observerList)
        {
            observer.Notify();
        }
    }
}
profile
개발 박살내자

0개의 댓글