Observer 패턴

JJW·2024년 11월 29일
0

Unity

목록 보기
7/34

오늘은 Observer 패턴에 대해 알아보는 시간을 가지겠습니다.


Observer 패턴이란 ?

  • 객체의 상태 변화를 관찰하는 "Observer"들이 해당 상태 변화에 반응할 수 있도록 설계 된 디자인 패턴입니다.
  • 객체 간 1 대 N 의존성을 정의하여 한 객체의 상태가 변경되었을 때, 의존하는 다른 객체들에게 자동으로 알림을 보낼 수 있게 하는 패턴입니다.

주요 구성 요소

1. Subject (주체)

public interface ISubject
{
    void Attach(IObserver observer); 	// 옵저버 등록
    void Detach(IObserver observer); 	// 옵저버 제거
    void Notify();                   	// 상태 변경 알림
}
  • 상태를 관리하고 옵저버들에게 알림을 보내는 객체입니다.
  • 옵저버들을 등록하거나 제거하는 인터페이스를 제공합니다.
  • 상태가 변경되면 옵저버들에게 변경 내용을 알립니다.

2. Observer (관찰자)

public interface IObserver
{
    void Update(int state); // 상태 변경 시 수행할 작업
}
  • Subject를 관찰하며, Subject의 상태 변경에 반응하는 객체입니다.
  • 상태 변경에 따라 특정 행동을 수행합니다.

3. ConcreteSubject (구체적 주체)

public class GameManager : MonoBehaviour, ISubject
{
    private List<IObserver> observers = new List<IObserver>();
    private int gameState;

    public int GameState
    {
        get => gameState;
        set
        {
            gameState = value;
            Notify(); // 상태 변경 시 모든 옵저버에게 알림
        }
    }

    public void Attach(IObserver observer)
    {
        observers.Add(observer);
    }

    public void Detach(IObserver observer)
    {
        observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in observers)
        {
            observer.Update(gameState);
        }
    }
}
  • Subject를 구체적으로 구현한 클래스입니다.
  • 상태를 저장하며, 상태가 변경되면 옵저버들에게 알립니다.

4. ConcreteObserver (구체적 관찰자)

public class UIManager : MonoBehaviour, IObserver
{
    public void Update(int score)
    {
        Debug.Log($"UI Updated: Score is now {score}");
    }
}

public class SoundManager : MonoBehaviour, IObserver
{
    public void Update(int score)
    {
        Debug.Log($"Play Sound for new score: {score}");
    }
}
  • Observer를 구체적으로 구현한 클래스입니다.
  • Subject의 상태를 반영해 자신의 상태를 업데이트하거나 특정 작업을 수행합니다.

Observer 패턴의 흐름

  • Subject에 Observer 등록
  • 상태 변경
  • 알림 전송
  • Observer 업데이트

위 코드 기준으로 예시를 들자면

  • Attach() -> Subject에 Observer 등록
  • GameState get,set : 상태 변경
  • Notify() : 알림 전송
  • foreach (var observer in observers) : Observer 업데이트

Observer 패턴 장점과 단점

  • 장점

    • 느슨한 결합
      • Subject와 Observer는 서로 직접적인 의존성을 가지지 않습니다. Subject는 Observer의 구체적인 동작을 알 필요가 없으며, Observer는 Subject의 상태 변경만 반응합니다.
    • 확장성
      • 새로운 Observer를 추가하거나 제거하는 일이 용이합니다.
    • 효율적인 알림 시스템
      • 여러 Observer들에게 자동으로 상태 변경을 전달할 수 있어, 수동으로 갱신할 필요가 없습니다.
  • 단점

    • 복잡성 증가
      • 작은 프로젝트에서는 불필요하게 느껴질 정도로 설계가 복잡해질 수 있습니다.
    • 성능 문제
      • 등록된 Observer가 많거나 이벤트 호출이 빈번할 경우, 퍼포먼스에 영향을 미칠 수 있습니다.
    • 디버깅 어려움
      • 이벤트 흐름이 비동기적으로 이루어지기 때문에 디버깅이 어려울 수 있습니다.

Observer 패턴의 사용 사례 및 Unity에서의 사용

  • 사용자의 입력처리 시 여러 컴포넌트가 반응하도록 설계합니다.
  • 플레이어의 상태가 바뀔 때 UI 업데이트나 사운드 효과를 동시에 처리합니다.
  • Unity에서는 이미 Action, UnityEvent가 이미 옵저버 패턴을 구현한 메커니즘이라. 옵저버 패턴을 직접 구현 할 필요가 없습니다.

Action

public class GameManager : MonoBehaviour
{
    public Action<int> OnScoreChanged;

    private int score;

    public void AddScore(int value)
    {
        score += value;
        OnScoreChanged?.Invoke(score); // 옵저버들에게 알림
    }
}

public class UIManager : MonoBehaviour
{
    public GameManager gameManager;

    private void Start()
    {
        gameManager.OnScoreChanged += UpdateUI; // 옵저버 등록
    }

    private void OnDestroy()
    {
        gameManager.OnScoreChanged -= UpdateUI; // 옵저버 해제
    }

    private void UpdateUI(int newScore)
    {
        Debug.Log($"Score Updated: {newScore}");
    }
}

UnityEvent

public class GameManager : MonoBehaviour
{
    public UnityEvent<int> OnScoreChanged;

    private int score;

    public void AddScore(int value)
    {
        score += value;
        OnScoreChanged?.Invoke(score); // 옵저버들에게 알림
    }
}

public class UIManager : MonoBehaviour
{
    public GameManager gameManager;

    private void Start()
    {
        gameManager.OnScoreChanged.AddListener(UpdateUI); // 옵저버 등록
    }

    private void OnDestroy()
    {
        gameManager.OnScoreChanged.RemoveListener(UpdateUI); // 옵저버 해제
    }

    private void UpdateUI(int newScore)
    {
        Debug.Log($"Score Updated: {newScore}");
    }
}
  • 직접 구현해서 사용하면 확장성과 세부적인 기능까지 제어가 가능하고
    Unity에서 제공해주는 Action이나 UnityEvent의 경우 쉽게 사용이 가능하고 안정성을 보장합니다.
  • 자신의 프로젝트가 공부 목적이거나 세부적인 기능까지 조절해야 한다면
    직접 구현해서 사용하면서 Observer패턴의 구조와 동작을 공부하는게 좋아보이고
    그게 아니라면 UnityEvent나 Action을 사용하는게 좋아 보입니다.

테스트

AddState를 누르면 Subject의 값의 변동이 발생해 Notify()를 호출하여
구독된 IObserver 들에게 Update를 명령합니다.


느낀 점

Action과 UnityEvent들이 이미 Observer 패턴이 구현 된 메커니즘이라는게 신기했습니다.
사용 방식이 비슷하다고는 느꼈지만 실제로 찾아보니 더욱 신기하네요.. 더 배워야 한다는 걸 느꼈습니다..

  • 제가 조사한 내용이 맞지 않거나 잘못 된 경우에 댓글로 잘못된 점 지적해주시면 감사합니다 ! (´._.`)
profile
Unity 게임 개발자를 준비하는 취업준비생입니다..

0개의 댓글