옵서버 패턴

정선호·2023년 5월 9일
0

Design Patterns

목록 보기
7/24

관련 영상

옵서버 패턴

위키피디아
설명 및 스도코드

  • 객체의 상태 변화를 관찰하는 관찰자들, 즉 옵저버들의 목록을 객체에 등록하여 상태 변화가 있을 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버에게 통지하도록 하는 디자인 패턴
    • 주로 분산 이벤트 핸들링 시스템을 구현하는 데 사용됨

옵서버 패턴의 구조

  • 출판사(Publisher)
    • 다른 객체들에 관심 이벤트들을 발행
    • 이벤트들은 출판사가 상태를 전환하거나 어떤 행동들을 실행할 때 발생
    • 출판사들에는 구독 인프라가 포함되어 있고, 이 인프라는 현재 구독자들이 리스트를 떠나고 새 구독자들이 리스트에 가입할 수 있도록 함
  • 새 이벤트 발생시 출판사는 구독자 리스트를 살펴본 후 각 구독자 객체의 구독자 인터페이스에 선언된 알림 메서드 호출
  • 구독자(Subscriber) 인터페이스
    • 알림 인터페이스를 선언하며 대부분의 경우 단일 update메서드로 구성됨
    • 이 메서드에서는 출판사가 업데이트와 함께 어떤 이벤트의 세부 정보들을 전달할 수 있도록 하는 여러 매개변수가 있을 수 있음
  • 구상 구독자(Concrete Subscribers)
    • 출판사가 보낸 알림들에 대한 응답으로 몇 가지 작업을 수행
    • 모든 클래스는 출판사가 구상 클래스들과 결합하지 않도록 같은 인터페이스를 구현해야 함
  • 일반적으로 구독자들이 업데이트를 올바르게 처리하기 위해 콘텍스트 정보가 어느 정도 필요로 하게 됨. 따라서 출판사들은 종종 콘텍스트 데이터를 알림 메서드의 인수들로 전달함. 출판사는 자신을 인수로 전달할 수 있으며 구독자가 필요한 데이터를 직접 가져오도록 함
  • 클라이언트
    • 출판사 및 구독자 객체들을 별도로 생성한 후 구독자들을 출판사 업데이트에 등록

옵서버 패턴의 적용

  • 한 객체의 상태가 변경되어 다른 객체들을 변경해야 할 필요성이 생겼을 때, 그리고 실제 객체 집합들을 미리 알 수 없거나 이러한 집합들이 동적으로 변경될 때 사용
    • GUI 클래스와 작업할 때 자주 경험 가능
  • 앱의 일부 객체들이 제한된 시간 동안 또는 특정 경우에만 다른 객체들을 관찰해야 할 때
    • 구독 리스트는 동적이므로 구독자들은 필요할때마다 리스트에 가입하거나 탈퇴 가능

다른 패턴과의 관계

  • 커맨드, 중재자, 옵서버, 책임 연쇄는 요청의 발신자와 수신자를 연결하는 다양한 방법을 다룬다.
    • 책임 연쇄 패턴은 잠재적 수신자의 동적 체인을 따라 수신자 중 하나에 의해 요청이 처리될 때까지 요청을 순차적으로 전달
    • 커맨드 패턴은 발신자와 수신자 간의 단방향 연결을 설립
    • 중재자 패턴은 발신자와 수신자 간의 직접 연결을 제거하여 그들이 중재자 객체를 통해 간접적으로 통신하도록 강제
    • 옵서버 패턴은 수신자들이 요청들의 수신을 동적으로 구독 및 구독 취소할 수 있도록 함
  • 중재자와 옵서버는 때로는 따로, 때로는 동시에 적용 가능함
    • 중재자의 주목적은 시스템 컴포넌트들의 집합 간의 상호 의존성을 제거하는 것. 그러면 이러한 컴포넌트들은 대신 단일 중재자 객체에 의존하게 되는데 옵서버 패턴의 목적은 객체들 사이에 단방향 연결을 설정하는 것으로, 여기서 일부 객체는 다른 객체의 종속자 역할을 하게 됨
    • 중재자 객체가 출판사의 역할을 맡고 컴포넌트들은 중재자의 이벤트들을 구독 및 구독 취소하는 구독자들의 역할을 맡으면 중재자 패턴이 옵서버 패턴과 매우 유사해짐
    • 모든 컴포넌트를 영구적으로 같은 중재자에 묶는 것은 옵서버 패턴이 아니지만 여전히 중재자 패턴임

유니티로 구현한 파라미터를 받지 않는 옵서버

  • 구독자 인터페이스
public abstract class Observer
{
    // 상태 update 메서드
    public abstract void OnNotify();
}
  • 구상 구독자
// 옵저버 구현클래스
public class ConcreteObserver1 : Observer
{
    // 대상타입의 클래스에서 이 메소드를 실행시킴
    public override void OnNotify()
    {
        Debug.Log("옵저버 클래스의 메서드 실행 #1");
    }
}

// 옵저버 구현클래스
public class ConcreteObserver2 : Observer
{
    // 대상타입의 클래스에서 이 메소드를 실행시킴
    public override void OnNotify()
    {
        Debug.Log("옵저버 클래스의 메서드 실행 #2");
    }
}
  • 출판사 인터페이스
// : 옵저버 관리, 활용에 관한 타입 정의
public interface ISubject
{
    void AddObserver(Observer o); // 구독자 추가
    void RemoveObserver(Observer o); // 구독자 삭제
    void Notify(); // 구독자에 연락하는 함수
}
  • 출판사 클래스
// 대상 인터페이스를 구현한 클래스
public class ConcreteSubject : MonoBehaviour, ISubject
{
    List<Observer> observers = new List<Observer>();  // 옵저버를 관리하는 List

    // 관리할 옵저버를 등록
    public void AddObserver(Observer observer)
    {
        observers.Add(observer);
    }

    // 관리중인 옵저버를 삭제
    public void RemoveObserver(Observer observer)
    {
        if (observers.IndexOf(observer) > 0) observers.Remove(observer);
    }

    // 관리중인 옵저버에게 연락
    public void Notify()
    {
          //for (int i = 0; i < observers.Count; i++)
          //{
              //observers[i].OnNotify();
          //}
		foreach (Observer o in observers)
		{
			o.OnNotify();
		}
    }

    void Start()
    {
        Observer obj1 = new ConcreteObserver1();
        Observer obj2 = new ConcreteObserver2();

        AddObserver(obj1);
        AddObserver(obj2);
    }
}

C# 델리게이트와 인자 전달을 실시하는 유니티 옵서버

  • 구독자 인터페이스
public abstract class Observer
{
    // 상태 update 메서드
    public abstract void OnNotify(int num);
}
  • 구상 구독자
// 옵저버 구현클래스
public class ConcreteObserver1 : Observer
{
    GameObject obj;

    // 생성자를 통해 객체 전달
    public ConcreteObserver1(GameObject obj)
    {
        this.obj = obj;
    }

    // 대상타입의 클래스에서 이 메소드를 실행시킴
    public override void OnNotify(int num)
    {
        int num2 = obj.gameObject.GetComponent<ConcreteSubject>().getNum();

        Debug.Log("옵저버 클래스의 메서드 실행 #1");
        Debug.Log("메서드의 파라미터 : " + num);
        Debug.Log("객체 변수를 통한 접근 : " + num2);
    }
}

// 옵저버 구현클래스
public class ConcreteObserver2 : Observer
{
    GameObject obj;

    // 생성자를 통해 객체 전달
    public ConcreteObserver2(GameObject obj)
    {
        this.obj = obj;
    }

    // 대상타입의 클래스에서 이 메소드를 실행시킴
    public override void OnNotify(int num)
    {
        int num2 = obj.gameObject.GetComponent<ConcreteSubject>().getNum();

        Debug.Log("옵저버 클래스의 메서드 실행 #2");
        Debug.Log("메서드의 파라미터 : " + num);
        Debug.Log("객체 변수를 통한 접근 : " + num2);
    }
}
  • 출판사 인터페이스
// : 옵저버 관리, 활용에 관한 타입 정의
public interface ISubject
{
    void AddObserver(Observer o); // 구독자 추가
    void RemoveObserver(Observer o); // 구독자 삭제
    void Notify(); // 구독자에 연락하는 함수
}
  • 출판사 클래스
public class ConcreteSubject : MonoBehaviour
{
  //  List<Observer> observers = new List<Observer>();  // 옵저버를 관리하는 List
  
    private int myNum;

	  delegate void NotiHandler(int num);
	  NotiHandler _notiHandler;

    public void Notify()
    {
		    _notiHandler(myNum);
    }

    void Start()
    {
        myNum = 10;

        Observer obj1 = new ConcreteObserver1(this.gameObject);
        Observer obj2 = new ConcreteObserver2(this.gameObject);

		    _notiHandler += new NotiHandler(obj1.OnNotify);
		    _notiHandler += new NotiHandler(obj2.OnNotify);
    }

    public int getNum()
    {
        return myNum;
    }
}
profile
학습한 내용을 빠르게 다시 찾기 위한 저장소

0개의 댓글