관찰자 패턴(Observer Pattern)#
- 관찰자 패턴은 매우 유용하고 자주 사용되는 패턴입니다.
- 어떤 특정한 상태 또는 조건에 따라 코드의 로직이 달라지는 경우, 그 상태와 조건을 지속적으로 확인해야 합니다.
- 일반적으로 아래와 같은 형태로 작성하곤 합니다.
while(true)
{
if (status == A)
{
doCodeA();
break;
}
else if (status == B)
{
doCodeB();
break;
}
}
- 어떤 특정한 상태
status
의 값을 지속적으로 확인하고, 해당 경우에 로직을 분기합니다. - 하지만 이러한 무한 루프는 지속적으로 상태를 관찰할 수 있지만, 관찰과 동시에 다른 동작을 수행할 수 없습니다.
- 관찰자 패턴은 상태의 관찰을 능동적으로 수행하는 것이 아닌, 상태의 변화를 수동적으로 수신합니다. 즉, 상태가 변경될 때 해당 정보를 수행합니다.
- 관찰자 패턴은 이런 특성으로 인해 게시(Publish)&구독(Subscribe) 또는 리스너(Listener) 패턴이라고도 부릅니다.
관찰자 패턴의 구조#
- 관찰자 패턴은 관찰자 객체의 등록, 삭제, 통보를 담당하는 주체(Subject) 클래스와 주체 클래스로부터 갱신된 정보를 받아 이를 처리하는 관찰자(Observer) 객체로 구분합니다.
- 주체 클래스와 관찰자 클래스는 공통된 처리를 위해 모두 인터페이스 클래스로 작성합니다.
- 따라서 아래와 같은 구조로 구성됩니다.
- Subject
- ConcreteSubject
- Observer
- ConcreteObserver
관찰자 패턴: 주체(Subject) 인터페이스 클래스#
- 주체 클래스는 관찰자 객체의 등록, 삭제, 통보를 담당합니다.
- 객체의 공통된 처리를 위해 인터페이스 클래스로 선언합니다.
class Subject
{
public:
virtual void addObserver(std::shared_ptr<Observer> observer) = 0;
virtual void deleteObserver(std::shared_ptr<Observer> observer) = 0;
virtual void notiObserver() = 0;
};
관찰자 패턴: 주체 클래스 구현#
- 인터페이스 클래스를 구체화하여 실제 구현을 진행합니다.
- 주체 객체는 내부적으로 관찰자 객체를 보관하는 컨테이너를 갖고 있습니다.
- 따라서 하나의 주체는 다수의 관찰자 객체와의 관계가 설정됩니다.
class ConcreteSubject :
public Subject
{
public:
void addObserver(std::shared_ptr<Observer> observer) override
{
std::cout << "[ConcreteSubject] addObserver()" << std::endl;
m_observers.push_back(observer);
}
void deleteObserver(std::shared_ptr<Observer> observer) override
{
for (auto iter = m_observers.begin(); iter != m_observers.end(); iter++)
{
if (*iter == observer)
{
m_observers.erase(iter);
break;
}
}
}
void notiObserver() override
{
for (const auto& observer : m_observers)
{
observer->update();
}
}
private:
std::vector<std::shared_ptr<Observer>> m_observers;
};
관찰자 패턴: 관찰자(Observer) 인터페이스 클래스#
- 관찰자 객체는 주체로부터 갱신된 상태 정보를 받고, 처리하는 객체입니다.
- 주체 클래스로부터 공통된 상태 정보 갱신을 위해 인터페이스 클래스로 선언합니다.
class Observer
{
public:
virtual void update() = 0;
protected:
std::string m_name;
};
관찰자 패턴: 관찰자 클래스 구현#
- 인터페이스 클래스를 구체화하여 실제 구현을 진행합니다.
// ConcreteObserver
class UserA :
public Observer
{
public:
UserA(std::string name)
{
m_name = name;
}
void update() override
{
std::cout << m_name << ", updated" << std::endl;
}
private:
};
// ConcreteObserver
class UserB :
public Observer
{
public:
UserB(std::string name)
{
m_name = name;
}
void update() override
{
std::cout << m_name << ", updated" << std::endl;
}
private:
};
관찰자 패턴: 실행 코드#
int main(const int argc, const char* argv[])
{
std::shared_ptr<ConcreteSubject> members = std::make_shared<ConcreteSubject>();
std::shared_ptr<UserA> userA = std::make_shared<UserA>("User A");
std::shared_ptr<UserB> userB = std::make_shared<UserB>("User B");
members->addObserver(userA);
members->addObserver(userB);
members->notiObserver();
return 0;
}
[ConcreteSubject] addObserver()
[ConcreteSubject] addObserver()
User A, updated
User B, updated