내일배움캠프 4주차 2일차 TIL - 옵서버 패턴

백흰범·2024년 5월 8일
0
post-thumbnail

오늘 한 일

  • 팀 노션 페이지 짜기
  • 스파르타 코딩클럽 진도 나가기 (~1-6)
  • 옵서버 패턴에 대해 분석하기

오늘은 강의 내용에서 새로 알게된 디자인 패턴 옵저버 패턴에 대해서 알아보려고 합니다. 자료를 찾아보면서 여러가지 좋은 설명들을 보고 그걸 토대로 제가 해석한 결과를 적어볼려고 합니다.


옵서버 패턴

설명

알림 메서드(event를 활용한)가 있는 특정 대상에게 옵저버를 붙여서 대상자의 정보나 상태가 변했을 때 대상자는 그 정보를 자신에게 연결된 모든 옵저버에게 신호를 보내고 그 신호를 받은 옵저버들은 전달 받은 정보를 토대로 자신의 기능을 작동시킨다.


개념적인 구조

옵서버 패턴의 인터페이스

// 옵서버의 인터페이스
 public interface IObserver
 {
     // 대상자로부터 업데이트를 받습니다.
     void Update(ISubject subject);
 }

 // 발신자, 대상자의 인터페이스
   public interface ISubject
  {
  	  // 인터페이스에 필드를 선언할 수는 없지만 이 인터페이스를 상속 받은 
      // 발신자 클래스들은 List<IObserver>의 필드를 가진다.

      // 대상자에게 옵저버를 붙입니다.
      void Attach(IObserver observer);
      // 대상자로부터 옵저버를 분리합니다.
      void Detach(IObserver observer);
      // 모든 옵저버에게 이벤트에 대해 알립니다.
      void Notify();
  }

옵서버 추가

// 발신자(subject) class 
// 옵저버 관리 메서드
public void Attach(IObserver observer)
{
    Console.WriteLine("주체: 옵저버를 연결합니다.");
    this._observers.Add(observer);
}

public void Detach(IObserver observer)
{
    this._observers.Remove(observer);
    Console.WriteLine("주제: 옵저버를 분리합니다.");
}

 var subject = new Subject(); // 발신자(subject) 인스턴스화

 // 옵서버 인스턴스화 및 발신자 구독
 var observerA = new ConcreteObserverA();
 subject.Attach(observerA);

 var observerB = new ConcreteObserverB();
 subject.Attach(observerB);

옵서버 리스트 내에 있는 모든 옵서버에게 알림

// 발신자 (Subject)
// 특정 정보 또는 상태
public int State { get; set; } = -0;

// 구독자들에게 업데이트를 날립니다.
public void Notify()
{
	Console.WriteLine("발신자: 옵저버들에게 알립니다...");

	foreach (var observer in _observers)
	{
		observer.Update(this);
	}
}

// 보통, 구독 로직은 오직 대상자가 할 수 있는 일의 일부분입니다.
// 대상자는 일반적으로 중요한 작업수행 로직을 가지고 있으며 
// 중요한 작업 수행이 벌어졌을 때 알림 메서드를 작동한다. (또는 그 이후에)
public void SomeBusinessLogic()
{
    Console.WriteLine("\n발신자: 중요한 일을 하고 있습니다.");
    this.State = new Random().Next(0, 10);

    Thread.Sleep(15);

    Console.WriteLine("발신자: 저의 정보가 이와 같이 변했습니다: " + this.State);
	this.Notify(); // 알림 메서드 발동
}

  // 구체적인 옵저버들은 자기가 구독한 대상자로부터 발신된 업데이트에 반응합니다.
  class ConcreteObserverA : IObserver
  {
      public void Update(ISubject subject)
      {
          if ((subject as Subject).State < 3)  // 값에 따라서 출력하는 조건을 붙여놓았다
          {
              Console.WriteLine("구체적인관찰자A: 이벤트에 반응했습니다.");
          }
      }
  }

  class ConcreteObserverB : IObserver
  {
      public void Update(ISubject subject)
      {
          if ((subject as Subject).State == 0 || (subject as Subject).State >= 2)
          {
              Console.WriteLine("구체적인관찰자B: 이벤트에 반응했습니다.");
          }
      }
  }

// Program.Main
   subject.SomeBusinessLogic(); // 알림 메서드가 들어간 메서드 발동
   subject.SomeBusinessLogic();

출력 결과

  • 출력을 보면 옵서버가 정보를 무작정 받아들이는 게 아니라 자신의 조건문에 따라서 출력을 결정한다. (물론 들어오는 신호를 받고 싶지 않다면 Detach 메서드를 이용해주면 된다.)

옵서버 분리

// Program.main
subject.Detach(observerB); // 옵저버를 제거하는 메서드

subject.SomeBusinessLogic();

출력 결과

  • 0 또는 2 이상의 정보를 받으면 작동하는 B옵저버의 Console.WriteLine()이 호출되지 않았다.

장단점

장점

  • 비동기적으로 상태 변경을 감지할 수 있다. (주기적인 조회를 통한 데이터 낭비 최소화)
  • 런타임 시점에서 원하는 기능을 자유롭게 연결 해제할 수 있다.
  • 발신자의 코드를 변경하지 않고도 새 클래스들을 옵저버 리스트를 통해서 기능을 추가할 수 있다. (개방/폐쇄의 원칙)

단점

  • 연결된 옵서버들의 알림이 무작위로 받는다. (추가되는 방식으로 옵저버를 연결하면 정확히는 들어온 순서대로 호출되긴 한다.) [하지만 어렵다고 한다..]
  • 다수의 옵저버 객체를 등록 이후 해지하지 않는다면 불필요한 메모리 누수가 발생할 수도 있다.

주 사용 용도

  • 대상 객체의 상태를 감지할 필요가 있을 경우 (체력이나 몬스터 처치 횟수 등등)
  • 하나의 정보를 여럿이서 받아야하는 상황일 때 (조작키로 받은 데이터를 -> 플레이어의 이동, 플레이어의 애니메이션, 몬스터의 플레이어 추적에 사용해야할 때 등등)
  • 런타임 중에 유동적으로 기능을 연결 분리해야할 때 (퀘스트, 특정 아이템의 기능)

참고 자료


강의에서 본 코드 해석하기 (6강 내용)

  • 한 번에 코드를 다 치면 내용이 너무 많으니 UML과 같이 간략히 정리해보았다.
  • 보라색은 입력 처리를 받는 특수 에셋, 노란색은 클래스 스크립트이다.
    (특수 에셋에게 OnMove, OnLook, OnFire의 이벤트 함수가 있다)
  • 아직은 TopDownController가 하나의 클래스에만 정보를 전달하고 있지만, 다른 기능의 클래스가 플레이어의 입력 처리 정보가 필요할 경우에 TopDownController의 수정 없이 추가적으로 등록하여 정보를 받아올 수 있다. (확장성이 매우 좋다)



작성하면서 느낀점

유니티 수업을 들으면서 기초를 잘 다져놓지 않으면 이해 못했을 내용들이 수두룩하다고 생각한다. 그래서 TIL을 작성하고 난다면 주기적으로도 부족한 부분들을 확인하고 꾸준히 채워나아가야만 앞으로의 개발 실력을 늘려나갈 수 있는 길이라 생각한다.

profile
게임 개발 꿈나무

0개의 댓글