관찰자 패턴(Observer Pattern)

  • 관찰자 패턴은 매우 유용하고 자주 사용되는 패턴입니다.
  • 어떤 특정한 상태 또는 조건에 따라 코드의 로직이 달라지는 경우, 그 상태와 조건을 지속적으로 확인해야 합니다.
  • 일반적으로 아래와 같은 형태로 작성하곤 합니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
while(true)
{
    if (status == A)
    {
        doCodeA();
        break;
    }
    else if (status == B)
    {
        doCodeB();
        break;
    }
}
  • 어떤 특정한 상태 status의 값을 지속적으로 확인하고, 해당 경우에 로직을 분기합니다.
  • 하지만 이러한 무한 루프는 지속적으로 상태를 관찰할 수 있지만, 관찰과 동시에 다른 동작을 수행할 수 없습니다.
  • 관찰자 패턴은 상태의 관찰을 능동적으로 수행하는 것이 아닌, 상태의 변화를 수동적으로 수신합니다. 즉, 상태가 변경될 때 해당 정보를 수행합니다.
  • 관찰자 패턴은 이런 특성으로 인해 게시(Publish)&구독(Subscribe) 또는 리스너(Listener) 패턴이라고도 부릅니다.

관찰자 패턴의 구조

  • 관찰자 패턴은 관찰자 객체의 등록, 삭제, 통보를 담당하는 주체(Subject) 클래스와 주체 클래스로부터 갱신된 정보를 받아 이를 처리하는 관찰자(Observer) 객체로 구분합니다.
  • 주체 클래스와 관찰자 클래스는 공통된 처리를 위해 모두 인터페이스 클래스로 작성합니다.
  • 따라서 아래와 같은 구조로 구성됩니다.
    • Subject
    • ConcreteSubject
    • Observer
    • ConcreteObserver

관찰자 패턴: 주체(Subject) 인터페이스 클래스

  • 주체 클래스는 관찰자 객체의 등록, 삭제, 통보를 담당합니다.
  • 객체의 공통된 처리를 위해 인터페이스 클래스로 선언합니다.
1
2
3
4
5
6
7
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;
};

관찰자 패턴: 주체 클래스 구현

  • 인터페이스 클래스를 구체화하여 실제 구현을 진행합니다.
  • 주체 객체는 내부적으로 관찰자 객체를 보관하는 컨테이너를 갖고 있습니다.
  • 따라서 하나의 주체는 다수의 관찰자 객체와의 관계가 설정됩니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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) 인터페이스 클래스

  • 관찰자 객체는 주체로부터 갱신된 상태 정보를 받고, 처리하는 객체입니다.
  • 주체 클래스로부터 공통된 상태 정보 갱신을 위해 인터페이스 클래스로 선언합니다.
1
2
3
4
5
6
7
8
class Observer
{
public:
    virtual void update() = 0;

protected:
    std::string m_name;
};

관찰자 패턴: 관찰자 클래스 구현

  • 인터페이스 클래스를 구체화하여 실제 구현을 진행합니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 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:
};

관찰자 패턴: 실행 코드

  • 관찰자 패턴의 사용 방법을 알아봅니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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;
}
  • 실행 결과
1
2
3
4
[ConcreteSubject] addObserver()
[ConcreteSubject] addObserver()
User A, updated
User B, updated