프로그래밍 일반/디자인 패턴

관찰자 패턴(Observer Pattern)

지노윈 2020. 3. 4. 21:46
반응형

SNS에서 Follow하여 구독하거나 구독 취소를 하는 것이 옵저버 패턴과 동일한 행위입니다.

옵저버 패턴은 실제 세상에서도 그 예가 많 듯이 어플리케이션 구현시에도 자주 필요하며 즐겨 사용되는 디자인 패턴입니다.

 

Person 클래스의 변경 사항을 모니터링하는 Person Observer의 구현을 살펴 보겠습니다.

age에 쓰기 작업이 있을때마다 콘솔 메시지를 출력하는 구현입니다.

이 구현은 전형적인 옵저버 패턴의 형태입니다.

(책에서 과제로 남겼던, std::any를 이용하여 제너릭으로 구현하였습니다)

template<typename T>struct Observer
{
 virtual void field_changed(T& source, const string& property_name, const any new_value) = 0;
};
 
struct ConsolePersonObserver : Observer<Person>       // Person 옵저버 구현, event 형태로 변경 통지 받음
{
 void field_changed(Person& p, const string& property_name, const any new_value) override
 {
  cout << "person's " << property_name << " has been changed to ";
  if (property_name == "age")
  {
   cout << any_cast<int>(new_value);
  }
  else if (property_name == "can_vote")
  {
   cout << any_cast<bool>(new_value);
  }
  cout << "\n";
 }
};

Observable 클래스는 Person을 모니터링 하려면 옵저버들 목록으로 관리 합니다.

옵저버가 Person의 변경 이벤트에 구독 등록 또는 구독 해제(subscribe()/ unsubscribe())를 할 수 있게 합니다.

notify()를 통해 변경 이벤트가 발생했을 때 모든 옵저버들에게 정보가 전달 되도록 합니다.

template<typename T> struct Observable
{
 void notify(T& source, const string& name, const any new_value)    // 구독자들에게 알린다
 {
  for (auto obs : observers)
   obs->field_changed(source, name, new_value);
 }
 
 void subscribe(Observer<T>* f)       // 구독 등록
 {
  observers.push_back(f);
 }
 
 void unsubsribe(Observer<T>* f)  // 구독 해제
 {
  if (observers.empty()) return;
  for (auto it = observers.begin(); it != observers.end(); ++it)
  {
   if (*it == f)
   {
    observers.erase(it);
    return;
   }
  }
 }
private:
 vector<Observer<T>*> observers;    // 구독자들
};
 
 
struct Person : Observable<Person>
{
 explicit Person(int age) : age(age)
 {
 }
 
 int get_age() const
 {
  return age;
 }
 
 void set_age(const int age)
 {
  if (this->age == age) return;
 
  auto old_c_v = get_can_vote();
 
  this->age = age;
  notify(*this, "age", age);
 
  auto new_c_v = get_can_vote();
  if (old_c_v != new_c_v)
  {
   notify(*this, "can_vote", new_c_v);
  }
 }
 
 bool get_can_vote() const
 {
  return age >= 16;
 }
   
private:
 int age;
};

다음과 같이 이용할 수 있습니다.

Person p{ 14 };
ConsolePersonObserver cpo;
p.subscribe(&cpo);
p.set_age(15);
p.set_age(16);
  • std::any
    copyable 타입이어야 한다.
    형식에 안전한(typesafe) void*
    any 객체에 담긴 값을 꺼낼 때는 std::any_cast라는 함수를 사용한다.
any str = string("str");
cout << any_cast<string>(str) << endl;
int i = any_cast<int>(str); // 형식이 일치하지 안흐면 bad_any_cast 예외 발생.

이전에 살펴 보았던 signal2를 이용하면 좀더 손쉽게 옵저버 패턴을 구현할 수도 있습니다.

 

 

 

[독서 리뷰] - 모던 C++ 디자인 패턴

 

모던 C++ 디자인 패턴

[객체 지향 프로그래밍/디자인 패턴] - 빌더 패턴(Builder Pattern) [분류 전체보기] - 디자인 패턴 [객체 지향 프로그래밍/디자인 패턴] - 팩토리 패턴(Factory pattern) [객체 지향 프로그래밍/디자인 패턴] -..

devjino.tistory.com

코드 참고 : https://cpp-design-patterns.readthedocs.io/en/latest/
 

Welcome to C++ Design Patterns’s documentation! — C++ Design Patterns 0.0.1 documentation

© Copyright 2018, Hans-J. Schmid Revision 786c83f8.

cpp-design-patterns.readthedocs.io

 

'프로그래밍 일반 > 디자인 패턴' 카테고리의 다른 글

전략 패턴(Strategy Pattern)  (0) 2020.03.04
상태 패턴(State Pattern)  (0) 2020.03.04
Null 객체  (0) 2020.03.04
메멘토 패턴(Memento Pattern)  (0) 2020.03.04
커맨드 패턴(Command Pattern)  (0) 2020.03.04