디자인 패턴

Observer 패턴

Keisa 2024. 9. 5. 14:41

의도

옵서버 패턴은 당신이 여러 객체에 자신이 관찰 중인 객체에 발생하는 모든 이벤트에 대하여 알리는 구독 메커니즘을 정의할 수 있도록 하는 행동 디자인 패턴입니다.

 문제

Customer​(손님) 및 Store​(가게)​라는 두 가지 유형의 객체들이 있다고 가정합니다. 손님은 곧 매장에 출시될 특정 브랜드의 제품​(예: 새 아이폰 모델)​에 매우 관심이 있습니다.

손님은 매일 매장을 방문하여 제품 재고를 확인할 수 있으나, 제품이 매장에 아직 운송되는 동안 이러한 방문 대부분은 무의미합니다.

매장 방문 vs. 스팸 발송

반면 매장에서는 새로운 제품이 출시될 때마다 모든 고객에게 스팸으로 간주할 수 있는 수많은 이메일을 보낼 수 있습니다. 이 수많은 이메일은 일부 고객들을 신제품 출시 확인을 위한 잦은 매장 방문으로부터 구출해낼 수 있으나, 동시에 신제품 출시에 관심이 없는 다른 고객들을 화나게 할 것입니다.

여기서 충돌이 발생합니다. 손님들이 신제품 출시 확인을 위해 시간을 낭비하든지, 매장들이 알림을 원하지 않는 고객들에게 신제품 출시를 알리며 자원을 낭비해야 합니다.

 해결책

시간이 지나면 변경될 수 있는 중요한 상태를 가진 객체가 있다고 가정해봅시다. 이 객체는 종종 ​(subject)라고 불립니다. 그러나 위 예시의 경우 이 객체는 자신의 상태에 대한 변경에 대해 다른 객체들에 알림을 보내는 역할도 맡을 것이니 해당 객체를 라고 부르겠습니다.

옵서버 패턴은 출판사 클래스에 개별 객체들이 그 출판사로부터 오는 이벤트들의 알림들을 구독 또는 구독 취소할 수 있도록 구독 메커니즘을 추가할 것을 제안합니다. 두려워하지 마세요. 그리 복잡하지 않습니다. 실제로 이 메커니즘은 1) 구독자 객체들에 대한 참조의 리스트를 저장하기 위한 배열 필드와 2) 그 리스트에 구독자들을 추가하거나 제거할 수 있도록 하는 여러 공개된​(public) 메서드들로 구성됩니다.

구독 메커니즘을 통해 개별 객체들이 이벤트 알림들을 구독할 수 있습니다.

이제 출판사에 중요한 이벤트가 발생할 때마다 구독자 리스트를 참조한 후 그들의 객체들에 있는 특정 알림 메서드를 호출합니다.

실제 앱에는 같은 출판사 클래스의 이벤트들을 추적하는 데 관심이 있는 수십 개의 서로 다른 구독자 클래스들이 있을 수 있습니다. 당신은 출판사를 이러한 모든 클래스에 결합하고 싶지 않을 것입니다. 게다가 당신은 당신의 출판사 클래스가 다른 사람들에 의해 사용되어야 한다면 이러한 구독자 클래스 중 일부는 미리 알지 못할 수도 있습니다.

그러므로 모든 구독자가 같은 인터페이스를 구현하고 출판사가 오직 그 인터페이스를 통해서만 구독자들과 통신하는 것이 매우 중요합니다. 이 인터페이스는 출판사가 알림과 어떤 콘텍스트 데이터를 전달하는 데 사용할 수 있는 매개변수들의 집합과 알림 메서드를 선언해야 합니다.

출판사는 특정 알림 메서드를 구독자들의 객체들에서부터 호출하여 그들에게 알림을 보냅니다.

당신의 앱에 여러 유형의 출판사가 있고 이들을 구독자들과 호환되도록 하려면 당신은 더 나아가 모든 출판사가 같은 인터페이스를 따르도록 할 수 있습니다. 이 인터페이스는 몇 가지 구독 메서드들만 설명하면 됩니다. 이 인터페이스를 통해 구독자들은 출판자들의 상태들을 그들의 구상 클래스들과 결합하지 않고 관찰할 수 있습니다.

 실제상황 적용

잡지 및 신문 구독.

당신이 신문이나 잡지를 구독한다면 다음 호가 있는지 확인하러 가게에 갈 필요가 없습니다. 대신 출판사가 발행 직후나 사전에 새 발행물을 구독자의 우편함으로 직접 보냅니다.

출판사는 구독자 리스트를 유지 관리하고 구독자들이 어떤 잡지에 관심 있는지 알고 있습니다. 출판사가 새로운 잡지의 발행호들를 보내는 것을 중단시키고 싶다면 구독자들은 언제든지 이 리스트를 떠날 수 있습니다.

 

의사코드

이 예시에서 옵서버 패턴은 텍스트 편집기 객체가 다른 서비스 객체들에 자신의 상태 변경에 대해 알릴 수 있도록 합니다.

다른 객체들에 발생하는 이벤트에 대해 객체들에 알립니다.

구독자 리스트는 동적으로 컴파일됩니다. 당신이 앱이 원하는 행동에 따라 객체들은 런타임 때 알림들을 받는 것을 시작하거나 중단할 수 있습니다.

이 구현에서 편집기 클래스는 자체적으로 구독 리스트를 유지 관리하지 않습니다. 편집기 클래스는 이 작업을 해당 작업을 전담하는 특수 도우미 객체에 위임합니다. 이 객체를 중앙 집중식 이벤트 디스패처 역할을 하도록 업그레이드하여 모든 객체가 출판사 역할을 하도록 할 수 있습니다.

앱에 새 구독자들을 추가할 때 기존 출판사 클래스들이 같은 인터페이스를 통해 모든 구독자와 작업하는 한 기존 출판사 클래스들은 변경할 필요가 없습니다.

 

/**
 * Observer Design Pattern
 *
 * Intent: Lets you define a subscription mechanism to notify multiple objects
 * about any events that happen to the object they're observing.
 *
 * Note that there's a lot of different terms with similar meaning associated
 * with this pattern. Just remember that the Subject is also called the
 * Publisher and the Observer is often called the Subscriber and vice versa.
 * Also the verbs "observe", "listen" or "track" usually mean the same thing.
 */

#include <iostream>
#include <list>
#include <string>

class IObserver {
 public:
  virtual ~IObserver(){};
  virtual void Update(const std::string &message_from_subject) = 0;
};

class ISubject {
 public:
  virtual ~ISubject(){};
  virtual void Attach(IObserver *observer) = 0;
  virtual void Detach(IObserver *observer) = 0;
  virtual void Notify() = 0;
};

/**
 * The Subject owns some important state and notifies observers when the state
 * changes.
 */

class Subject : public ISubject {
 public:
  virtual ~Subject() {
    std::cout << "Goodbye, I was the Subject.\n";
  }

  /**
   * The subscription management methods.
   */
  void Attach(IObserver *observer) override {
    list_observer_.push_back(observer);
  }
  void Detach(IObserver *observer) override {
    list_observer_.remove(observer);
  }
  void Notify() override {
    std::list<IObserver *>::iterator iterator = list_observer_.begin();
    HowManyObserver();
    while (iterator != list_observer_.end()) {
      (*iterator)->Update(message_);
      ++iterator;
    }
  }

  void CreateMessage(std::string message = "Empty") {
    this->message_ = message;
    Notify();
  }
  void HowManyObserver() {
    std::cout << "There are " << list_observer_.size() << " observers in the list.\n";
  }

  /**
   * Usually, the subscription logic is only a fraction of what a Subject can
   * really do. Subjects commonly hold some important business logic, that
   * triggers a notification method whenever something important is about to
   * happen (or after it).
   */
  void SomeBusinessLogic() {
    this->message_ = "change message message";
    Notify();
    std::cout << "I'm about to do some thing important\n";
  }

 private:
  std::list<IObserver *> list_observer_;
  std::string message_;
};

class Observer : public IObserver {
 public:
  Observer(Subject &subject) : subject_(subject) {
    this->subject_.Attach(this);
    std::cout << "Hi, I'm the Observer \"" << ++Observer::static_number_ << "\".\n";
    this->number_ = Observer::static_number_;
  }
  virtual ~Observer() {
    std::cout << "Goodbye, I was the Observer \"" << this->number_ << "\".\n";
  }

  void Update(const std::string &message_from_subject) override {
    message_from_subject_ = message_from_subject;
    PrintInfo();
  }
  void RemoveMeFromTheList() {
    subject_.Detach(this);
    std::cout << "Observer \"" << number_ << "\" removed from the list.\n";
  }
  void PrintInfo() {
    std::cout << "Observer \"" << this->number_ << "\": a new message is available --> " << this->message_from_subject_ << "\n";
  }

 private:
  std::string message_from_subject_;
  Subject &subject_;
  static int static_number_;
  int number_;
};

int Observer::static_number_ = 0;

void ClientCode() {
  Subject *subject = new Subject;
  Observer *observer1 = new Observer(*subject);
  Observer *observer2 = new Observer(*subject);
  Observer *observer3 = new Observer(*subject);
  Observer *observer4;
  Observer *observer5;

  subject->CreateMessage("Hello World! :D");
  observer3->RemoveMeFromTheList();

  subject->CreateMessage("The weather is hot today! :p");
  observer4 = new Observer(*subject);

  observer2->RemoveMeFromTheList();
  observer5 = new Observer(*subject);

  subject->CreateMessage("My new car is great! ;)");
  observer5->RemoveMeFromTheList();

  observer4->RemoveMeFromTheList();
  observer1->RemoveMeFromTheList();

  delete observer5;
  delete observer4;
  delete observer3;
  delete observer2;
  delete observer1;
  delete subject;
}

int main() {
  ClientCode();
  return 0;
}

'디자인 패턴' 카테고리의 다른 글

의존성 주입  (0) 2025.04.08
Memento 패턴  (3) 2024.09.05
Mediator 패턴  (0) 2024.09.05
iterator 패턴  (1) 2024.09.05
Command 패턴  (0) 2024.09.04