반응형
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를 이용하면 좀더 손쉽게 옵저버 패턴을 구현할 수도 있습니다.
코드 참고 : https://cpp-design-patterns.readthedocs.io/en/latest/
'프로그래밍 일반 > 디자인 패턴' 카테고리의 다른 글
전략 패턴(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 |