(C샵 유니티) Observer 패턴 (옵저버)

티원·2025년 10월 21일

디자인패턴

목록 보기
6/16

옵져버 패턴

옵져버 패턴 시연 동영상

  • 객체간 1:N 관계를 정의하여 한 객체의 상태가 변경되면 의존 객체들이 자동으로 알림을 받는 디자인 패턴
  • 발행자 <-> 구독자 구조
  • 느슨한 결합
  • 유니티에서는 UI이벤트 시스템, 알림 시스템 에 사용

delegate 가 생각나는 패턴
여러 객체가 등록해두고 이벤트 발생 시 자동으로 호출되는 구조면 옵저버 패턴

사용법 (이론)

Subject(관찰대상)와 observer(관찰자)가 있으며
Subject는 observer를 모아두고 특정 조건이 발생했을때 observer들 한테 특정 행동을 하라고 지시한다
1. 인터페이스를 만든다 (Observer가 가져야할 콜백 메서드 정의)
Subject에서 행동을 하라고 지시했을때 Observer에서 어떤 행동을 할것인지 작성하기위해서
2. observer(관찰자) 클래스를 만든다
앞서 만든 인터페이스를 상속하고, 실제 행동인 해당 기능을 구현한다.
Awake로 구독, OnDestroy로 반드시 구독을 해제해준다 (순환참조 방지위해서 Destroy)
3. Subject(관찰대상) 를 만든다.
List<인터페이스>를 만들어서 관찰자들의 인터페이스 정보를 저장해둔다.
특정 조건이 만족하면 foreach로 List를 순회하며 인터페이스에서 구현했던 메소드를 호출해준다 (콜백 호출)
Call Back : 어떤 이벤트나 동작이 일어난 후 호출되는 함수

사용법 (코드)

지금 코드는 Awake되면 자동으로 구독하는 구조인데 꼭 이렇게 안하고, 구독/알림구조라면 옵저버패턴임

왼쪽위의 TMP Text에 1초가 지날때마다 옵져버 패턴으로 갱신된다

추가 코드: delegate를 써서 Observer 패턴 구현

1.ITimeChange + UiTimeSet (인터페이스와 observer 코드)

//1. ITimeChange.cs 파일
public interface ITimeChange
{
    public void PrintNowTime(float fTime); //시간 설정
}
//2. UiTimeSet.cs 파일
public class UiTimeSet : MonoBehaviour, ITimeChange
{
    //UI에 시간을 넣기
    [SerializeField] TextMeshProUGUI guiText;  //TMP Text에 표시하기 위해서
    [SerializeField] TimerUi timerUi;   //어떤 관찰대상에서 값을 받아올것인지

    public void PrintNowTime(float fTime)
    {
        guiText.text = fTime.ToString(); //TMP Text 값 수정
    }

    private void Awake()
    {
        timerUi.AddTimeChange(this); //구독
    }
    private void OnDisable()
    {
        timerUi.RemoveTimeChange(this); //구독 해제
    }
}

2.TimerUi (Subject(관찰대상) 코드)

public class TimerUi : MonoBehaviour    //Subject 클래스
{
    //1초가 지날때마다 UI의 TMP Text가 변경, Interface는 OnTimeChange라는 것을 만들고
    //인터페이스를 관리하는 클래스에서 1초가 지난시점에 이벤트를 발생시킴
    //Observer(관찰자)들은 Awake시점에 해당 이벤트를 추가한다
    private List<ITimeChange> _timeChangeList = new List<ITimeChange>();    //이벤트 목록
    public void AddTimeChange(ITimeChange time) => _timeChangeList.Add(time); //관찰자들이 이벤트 추가할때 사용하는 메서드
    public void RemoveTimeChange(ITimeChange time) => _timeChangeList.Remove(time); //이벤트 삭제 시 추가하는 메서드
    private float currentTime = 0f; //현재 시간
    private int previousSecond = 0; //이전 시간 (초)
    private void Awake()
    {
        currentTime = 0f; //초기 시간 설정
    }
    private void Update() //1초마다 이벤트 보내기 위해서 사용
    {
        currentTime += Time.deltaTime;
        
        int currentSecond = Mathf.FloorToInt(currentTime); //소수점 이하 무조건 버린 숫자
        if(currentSecond > previousSecond) //1초마다 체크
        {
            Debug.Log(currentSecond + "초 경과");
            previousSecond = currentSecond;
            SetTimerInvoke(); //1초가 지날때마다 Observer들에게 알려줌
        }
        
    }

    private void SetTimerInvoke() //모든 Observer들에게 값을 넣어줌
    {
        foreach(ITimeChange item in _timeChangeList)
        {
            int currentSecond = Mathf.FloorToInt(currentTime);
            item.PrintNowTime(currentSecond);
        }
    }
}

왜쓰는가

객체간 상태변화를 자동으로 동기화 해야하는 상황에 사용
코드간 결합도를 낮추고, 확장성 높은 이벤트 기반 시스템 구성 가능
예시)

  • UI 버튼 클릭시 여러 시스템이 동시에 반응
  • 게임에서 플레이어 체력변화 -> UI, 사운드, 로그시스템 모두 업데이트
  • 데이터 변경 시 즉시 반영되어야 하는 알림(Notify)구조 필요할때

옵저버 패턴은 이런경우 발행자(Publisher)가 변화를 통보하고, 여러 구독자가 자동으로 반응

장점

  • 객체간 결합도 낮음 -> 유지보수 쉬움
  • 이벤트 흐름 자동화로 깔끔
  • 여러 구독자에게 효율적으로 알림 전달

단점

  • 구독자 관리 복잡 (메모리 누수 가능, 이벤트 호출했는데 받는사람이 이미 파괴된경우.) -> 생성과 파괴를 잘 해줘야한다
  • 디버깅 어려움 (누가 누구를 구독하는지 추적 어려움)
  • 순환 참조 위험 -> 두 객체가 서로를 참조하고 있는 상황 (서로가 서로를 참조한 상태라서 GC로 파괴가 안됨)

(옵져버 패턴아님)이벤트 버스 (Event Bus)

옵져버 패턴은 아니지만 비슷한 기능인데 중앙에서 관리한다는 점이 달라 여기에 정리.
지금은 이벤트버스가 있다는거만 알자 옵져버 패턴과 비슷하지만 옵져버패턴은 1:N으로 연결되고 발행자가 구독자를 알아야한다
하지만 이벤트 버스는 중앙의 EventBus라는 허브? 가 존재하고 발행자가 EventBus에 이벤트 발생을 알리면 버스가 구독한 애들한테 알아서 전달한다고 한다 (발행자와 구독자가 서로 몰라도됨)

키워드: 옵저버, 옵져버, observer

0개의 댓글