Chapter 12. 옵저버 패턴(Observer Pattern)

Date:     Updated:

카테고리:

태그:

인프런에 있는 이재환님의 강의 게임 디자인 패턴 with Unity 를 듣고 정리한 필기입니다. 😀

Chapter 12. Observer Pattern

🔔 개념

한 객체의 상태가 바뀌면 그 객체에 의존하는 다른 객체들한테 연락이 가서 자동으로 내용이 갱신되는 방식. 1:N 의존성을 정의한다.

image

image

Subject 객체가 bCheck 값 변경이 일어나면 Observer 객체들에게 이를 알린다. 각각의 Observer 객체들의 update() 함수에서 이를 감지하고 이에 따른 동작들을 수행한다.

  • Subject 가 Observer 에 대해서 아는 것은 Observer 가 특정 인터페이스를 구현한다는 것 뿐
    • 예를 들어 Observer 객체들은 모두 update() 함수를 가지며 이에서 변화를 감지한다.
  • Observer 는 언제든지 새로 추가할 수 있다.
    • Subject 는 Observer 인터페이스를 구현하는 객체 목록에만 의존하기 때문.
  • 새로운 형식의 Observer를 추가하려 해도 Subject 를 전혀 변경할 필요가 없다.
    • 새로운 클래스에서 Observer 인터페이스만 구현해주면 된다.
  • Subject 나 Observer 가 바뀌더라도 서로에게 전혀 영향을 주지 않는다. 그래서 Subject 와 Observer 는 서로 독립적으로 재사용할 수 있다.

서브젝트에서 옵저버들을 리스트로 관리를 하고, 이 리스트를 순회하며 옵저버들의 함수를 실행시키는 식으로 작동한다. 예를 들어 버튼 이벤트가 발생하면 서브젝트의 A 함수가 실행되고 이 A 함수는 옵저버 오브젝트들의 B 함수를 일괄적으로 실행시키는 식으로.


유니티, C# 에서의 델리게이트와 같다. 델리게이트가 곧 이 옵저버 패턴인 것이다. 델리게이트 사용하면 옵저버 객체들을 리스트로 관리할 필요가 없다.


🔔 예제 1

📜Observer

// 옵저버 추상클래스
// : 옵저버들이 구현해야 할 인터페이스 메서드
public abstract class Observer
{
    // 상태 update 메서드
    public abstract void OnNotify();
}


📜ConcreteObserver1

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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


📜ConcreteObserver2

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 옵저버 구현클래스
public class ConcreteObserver2 : Observer
{
    // 대상타입의 클래스에서 이 메소드를 실행시킴
    public override void OnNotify()
    {
        Debug.Log("옵저버 클래스의 메서드 실행 #2");
    }
}


📜Subject

// 대상 인터페이스
// : 옵저버 관리, 활용에 관한 타입 정의
public interface ISubject
{
    void AddObserver(Observer o);
    void RemoveObserver(Observer o);
    void Notify();
}

모든 Subject 인터페이스 자식 클래스들은 위 함수들으 ㄹ오버라이딩 해야 한다.

  • AddObserver
    • 메세지 뿌릴 옵저버 추가
  • RemoveObserver
    • 메세지 뿌릴 옵저버 삭제
  • Notify
    • 옵저버들에게 연락하는 함수


📜ConcreteSubject

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 대상 클래스
// : 대상 인터페이스를 구현한 클래스
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);
    }
}

  • 옵저버들을 리스트로 관리
  • Notify() 함수를 통해 옵저버들의 OnNotify() 함수를 다 실행시킴
    • 유니티 버튼 이벤트에 이 Notify() 함수를 추가하여, 버튼이 눌리면 옵저버들의 OnNotify() 함수가 실행되도록 한다.


🔔 예제 2

📜Observer

// 옵저버 추상클래스
// : 옵저버들이 구현해야 할 인터페이스 메서드
public abstract class Observer
{
    // 상태 update 메서드
    public abstract void OnNotify(int num);
}
  • 이번엔 Subject 로 부터 파라미터 데이터를 받는다.


📜ConcreteObserver1

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 옵저버 구현클래스
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);
    }
}


  • obj로 부터 서브젝트 📜ConcreteSubject 가 붙어있는 오브젝트를 받는다.
    • 📜ConcreteSubject의 함수와 데이터를 옵저버가 받기 위하여


📜ConcreteObserver2

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 옵저버 구현클래스
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);
    }
}

  • obj로 부터 서브젝트 📜ConcreteSubject 가 붙어있는 오브젝트를 받는다.
    • 📜ConcreteSubject의 함수와 데이터를 옵저버가 받기 위하여


📜Subject

// 대상 인터페이스
// : 옵저버 관리, 활용에 관한 타입 정의
public interface ISubject
{
    void AddObserver(Observer o);
    void RemoveObserver(Observer o);
    void Notify();
}

모든 Subject 인터페이스 자식 클래스들은 위 함수들으 ㄹ오버라이딩 해야 한다.

  • AddObserver
    • 메세지 뿌릴 옵저버 추가
  • RemoveObserver
    • 메세지 뿌릴 옵저버 삭제
  • Notify
    • 옵저버들에게 연락하는 함수


📜ConcreteSubject

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 대상 클래스
// : 대상 인터페이스를 구현한 클래스
public class ConcreteSubject : MonoBehaviour, ISubject
{
    List<Observer> observers = new List<Observer>();  // 옵저버를 관리하는 List
    private int myNum;

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

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

    // 관리중인 옵저버에게 연락
    public void Notify()
    {
		  foreach (Observer o in observers)
		  {
			  o.OnNotify(myNum); 
		  } 
    }

    void Start()
    {
        myNum = 10;

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

        AddObserver(obj1);
        AddObserver(obj2);
    }

    public int getNum()
    {
        return myNum;
    }
}


  • 옵저버들에게 myNum 데이터를 알려준다. o.OnNotify(myNum);
  • 옵저버들에게 자신의 오브젝트를 넘겨 this.gameObject, 이를 통해 📜ConcreteSubject 의 getNum() 함수를 사용할 수 있게 해준다.


🔔 예제 3 (delegate 사용)

나머지 코드들은 예제 2 와 일치하다.

📜ConcreteSubject

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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;
    }
}

  • int 파라미터 하나를 받는 void 형 함수들만 등록할 수 있는 _notiHandler 델리게이트 객체를 생성한다.
    • 📜ConcreteSubject의 Notify()는 이 델리게이트를 파라미터만 넘겨 실행시킬 뿐이다. 어떤 함수들이 등록되있는지 알 필요 없는 체로.
      • 이전에 개별적으로 리스트에 옵저버 객체들을 저장해두고 for문 돌려 일일이 옵저버들의 OnNotify를 실행시켰던 것과 달리 간단.
  • 델리게이트에 그저 옵저버의 OnNotify 함수를 등록시킬 뿐이다.
    		  _notiHandler += new NotiHandler(obj1.OnNotify);
              _notiHandler += new NotiHandler(obj2.OnNotify);
    


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Design Pattern 카테고리 내 다른 글 보러가기

댓글 남기기