디자인 패턴 : 옵저버 패턴

김동현·2022년 7월 19일
0

폴링 : 주기적으로 데이터 확인 해 주는것

옵저버 패턴

어떤 객체의 상태가 변할 때 그 객체에 의존성을 가진 다른 객체들이 그 변화를 통지 받아 자동으로 갱신될 수 있게 만드는 패턴이다.

옵저버 패턴을 사용할 수 있을 때

  • 한 객체가 다른 객체에 종속적일 때
  • 한 객체의 변경으로 다른 객체를 변경해야하고, 프로그래머가 변경되어야 하는 객체수를 몰라도 될때
  • 어떤 객체가 다른 객체에 자신의 변화를 통지할 수 있는데, 그 변화에 관심 있어 하는 객체가 누구인지에 대한 가정이 없어도 될 때

종속적 : A 라는 객체가 변화하면 B 나 C 도 같이 변화하는걸 의미

옵저버는 누군가를 구독할 수 있고 해제할 수 있다.

Subject는 자신의 변화를 옵저버에게 전달한다.

옵저버 패턴 구현

옵저버 패턴을 구현하는 방법은 다양하다. 제네릭을 이용할 수도 있고, 변화된 상태를 전달하는 것도 Subject가 같이 보내줄 수도 있고(Push Model), Observer가 알아서 꺼낼 수도 있다.(Pull Model). 핵심은 변화가 이뤄질 때, 나의 변화를 관찰할고 있는 대상들에게 통지를 해주면 된다. 요즘은 상속기반의 구현 뿐만 아니라 함수형을 이용하여 구현되기도 한다. C#의 이벤트가 그 예시이다.

장점

  1. Subject와 Observer가 강하게 결합되지 않는다.
  • 결합도가 높으면 유지보수가 힘들어진다.
  • 폴링을 이용하면 객체가 멤버로 Subject를 들고 있어야 해서 강하게 결합될 수 밖에 없다.
  • 옵저버 패턴은 이를 피할 수 있다.
  1. 브로드캐스트(Broadcast)가 가능하다
  • Subject는 특정 타입의 객체에 한정되지 않고 누구던지 관찰자에게 나의 변화를 통지할 수 있다.
  1. 예측하지 못한 데이터를 갱신할 수 있다.
  • 복잡한 시스템에서는 종속 관계가 복잡해질 수 있으며 추적하기가 무척 어렵다.
  • 폴링을 통해서는 코드로 작성하기가 무척 난감할 수 있으며 반드시 해야하는 갱신을 빼먹을 수도 있다.

단점

  1. 무효참조(Dangling Refrence)가 일어날 수 있다.
  • Subject로부터 통지를 받으려면 해당 Subject에 등록을 해야 한다.
  • 더이상 통지 받고 싶지 않을 때 해제를 해야 한다.
  • 만약 객체가 사라졌는데 Subject에 해지를 요청하지 않았다면 통지를 할 때 무효 참조 이슈가 발생할 수 있다. 이는 디버깅을 어렵게 한다.
  • 이미 수명이 끝난 객체를 가리킬 수 있다.
  1. 상태의 자체 일관성(self-consistency)이 깨질 수 있다.
  • 서브 클래스가 슈퍼 클래스의 연산을 사용하는 과정에서 통지가 일어날 수 있다.
  1. 통지에 따른 갱신이 오래 걸릴 수 있다.
  • 통지하는 시점이 병목이 될 수 있다.
  • 이를 완화하기 위해 비동기 방식을 사용하거나 큐잉(Queueing)을 이용할 수 있다.

유니티에서의 이벤트

using UnityEngine;
using UnityEngine.Events;
using System.Collections;

public class ExampleClass : MonoBehaviour
{
    UnityEvent m_MyEvent;

    void Start()
    {
        if (m_MyEvent == null)
            m_MyEvent = new UnityEvent();

        m_MyEvent.AddListener(Ping);
    }

    void Update()
    {
        if (Input.anyKeyDown && m_MyEvent != null)
        {
            m_MyEvent.Invoke();
        }
    }

    void Ping()
    {
        Debug.Log("Ping");
    }
}

C#과 유니티 이벤트 차이

    // C#의 이벤트 객체를 만들지 않아도 댐
    public event UnityAction InGameEnd2;
    public event UnityAction<int> OnScoreChanged2;

    // 유니티 이벤트
    public UnityEvent OnGameEnd = new UnityEvent();
    public UnityEvent<int> OnScoreChanged = new UnityEvent<int>();
    
    
        // Score에 대한 프로퍼티
    public int CurrentScore
    {
        get
        {
            // 스코어 반환
            return _currentScore;
        }
        set
        {
            // value : 프로퍼티를 구현할 때 사용가능
            // Score 의 값이라고 보면 됨.
            _currentScore = value;
            OnScoreChanged.Invoke(_currentScore);

            // C#의 이벤트는 널체크를 한다.
            // 구독자가 있는지 확인(널체크)
            OnScoreChanged2?.Invoke(_currentScore);
        }

    }
    
        public void End()
    {
        _isEnd = true;
        OnGameEnd.Invoke();
        // C#에서의 이벤트는 널체크를 해야 함
        OnGameEnd2?.Invoke();
        
    }
    
    void OnEnable()
    {
        // 혹시나 다른곳에서 AddLisrener()가 될 수 있으므로 구독해제를 해 준다.
        GameManager.Instance.OnScoreChanged.RemoveListener(UpdateText);
        // 이벤트 구독
        GameManager.Instance.OnScoreChanged.AddListener(UpdateText);

        // C# 에서의 이벤트 구독해제
        GameManager.Instance.OnScoreChanged2 -= UpdateText;
        // C# 에서의 이벤트 구독
        GameManager.Instance.OnScoreChanged2 += UpdateText; 
    }
    
    void OnDisable()
    {
        // 비 활성화 되면 이벤트 통지를 받을 필요가 없으므로 구독해제를 한것
        GameManager.Instance.OnScoreChanged.RemoveListener(UpdateText);

        // C#에서의 구독 해제
        GameManager.Instance.OnScoreChanged2 -= UpdateText;
    }
    

함수형 변수를 받아 사용하는것
AddListener() : 구독
구독은 +연산자로
구독해제는 -연산자로 한다.

유니런 코드 참조하기

profile
해보자요

0개의 댓글