Skip to main content

[Design Pattern] 행동 패턴 2. 옵저버 (Observer)




옵저버 패턴 #

옵저버

  • 주체와 관찰자 사이에 1 대 다 의존 관계를 정의해 둔다.
    • 이때 주체의 상태가 변하면, 그 객체의 의존성을 가진 관찰자 객체들이 그 변화를 통지 받고 자동으로 갱신된다.
  • 특징
    • 모델-뷰-컨트롤러(Model-View-Controller: MVC) 패턴의 기반에는 옵저버 패턴이 있다.
    • 워낙 흔하다보니 C#에서는 event 키워드로 지원하고, JAVA에서는 java.util.Observer에 들어가 있다.
    • 기존 코드를 변경하지 않고도 새로운 관찰자와 주체를 추가할 수 있다.
    • 런타임에 객체 간 관계를 설정할 수 있다.
    • 관찰자가 알림을 받는 순서는 보장되지 않는다.

게임에서의 예시 #

  • 업적 시스템을 만들고 싶다.
  • 예를 들어, ‘다리에서 떨어지기’ 업적이라면 충돌 검사 알고리즘을 통해 조건을 검사해야한다.
    • 하지만 이런 물리 코드와 업적 코드를 커플링하면 안 되겠다.
    • 이때 옵저버 패턴을 사용하면 좋다.
class Observer
{
public:
  virtual ~Observer() {}
  virtual void onNotify(const Entity& entity, Event event) = 0;
};

class Achievement : public Observer
{
private:
  void unlock(Achievement achievement)
  {
    // 업적을 잠금해제한다. 
  }
  
public:
  // 관찰자가 알림을 받는다.
  virtual void onNotify(const Entity& entity, Event event)
  {
    switch (event)
    {
    case EVENT_ENTITY_FELL:
      if (entity.isHero() && heroIsOnBridge) 
        unlock(ACHIEVEMENT_FELL_OFF_BEIDGE);
      break;
    
    // ...
    }
  }
}; 

class Subject
{
private:
  int numObservers;
  Observer* observers[MAX_OBSERVERS]; // 정적배열 대신 동적할당 컬렉션을 쓸 수도 있다. 

protected:
  // 주체의 상태가 변하면, 관찰자들에게 알림을 준다. 
  void notify(const Entity& entity, Event event)
  {
    for (int i = 0; i < numObservers; i++)
    {
      observers[i]->onNotify(entity, event);
    }
  }

public:
  // 동적할당 컬렉션을 쓰는 경우, 동적 할당/해제가 잦을 수 있다. 
  // 그럴 때는 Observer가 다음 Observer를 가리키도록 해서 해결할 수 있겠다. 
  void addObserver(Observer* observer);
  void removeObserver(Observer* observer);
};

class Physics : public Subject
{
public:
  void updateEntity(Entity& entity)
  {
    bool wasOnSurface = entity.isOnSurface();
    entity.accelerate(GRAVITY);
    entity.update();
    
    // 액터가 떨어졌을 때 관찰자들에게 알림을 준다. 
    if (wasOnSurface && entity.isOnSurface() == false)
      notify(entity, EVENT_ENTITY_FELL)
  }
}

개념적인 예시 #

// 주체 
public interface ISubject
{
  // 관찰자를 등록, 삭제하고,
  // 관찰자들에게 알림을 준다. 
  void Attach(IObserver observer);
  void Detach(IObserver observer);
  void Notify();
}

public class Subject : ISubject
{
  public int State { get; set; } = -0;

  private List<IObserver> _observers = new List<IObserver>();

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

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

  public void Notify()
  {
    foreach (var observer in _observers)
    {
      // 관찰자들에게 알림을 준다.  
      observer.Update(this);
    }
  }
  
  public void SomeBusinessLogic()
  {
    this.State = new Random().Next(0, 10);

    Thread.Sleep(15);

    Console.WriteLine("Subject: My state has just changed to: " + this.State);
    this.Notify();
  }
}

// 관찰자
public interface IObserver
{
  void Update(ISubject subject);
}

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가 반응한다.");
    }
  }
}

class Program
{
  static void Main(string[] args)
  {
    var subject = new Subject();
     
    var observerA = new ConcreteObserverA();
    subject.Attach(observerA);
    var observerB = new ConcreteObserverB();
    subject.Attach(observerB);
  
    // State == 2 -> A, B
    subject.SomeBusinessLogic();
    
    // State == 1 -> A
    subject.SomeBusinessLogic();

    // State == 2 -> A
    subject.Detach(observerB);
    subject.SomeBusinessLogic();
  }
}



References #