3 분 소요

Observer는 특정 개체를 감시하며, 변경 발생을 통지 받는 일반적인 구조입니다.(개체지향 원칙중 헐리우드 원칙과 유사합니다.)

설명

데이터와 이를 표시하는 View의 경우, 데이터 변경을 View에 통지하면, 해당 View에서 변경 사항을 수집하고 그려주게 됩니다.

image

이러한 관계를 Doc-View, Publish-Subscribe 라고도 합니다. 이렇게 데이터와 View단일 책임 원칙에 따라 각 역할에 맞춰 분리하면 개체의 책임이 줄어들고, 결합도는 낮아지며, 코드 수정시의 사이드 이펙트가 발생할 확률도 낮아집니다.

이때 개체간에 직접 참조를 한다면 상호 참조 문제가 발생할 수 있기 때문에, 데이터는 어떤 View가 감시하는지 모른채 통지하는게 중요합니다.

이러한 경우 Observer 패턴을 사용하여 상호 참조 문제를 해결할 수 있습니다.

다음 그림은 Observer의 일반적인 구조입니다.

ConcreteSubject에 변경 사항이 생기면, Notify()를 통해 Observer에 변경되었음을 통지합니다. 그러면, 각 ConcreteObserver에서는 변경된 내용을 처리하기 위해 GetState()SetState()를 호출하여 ConcreteSubject에 접근할 수 있습니다.

Observer

항목 내용
Subject 내용이 변경되었음을 Observer에 통지합니다.
Observer Subject를 감시합니다.
ConcreteSubject Subject를 구체화한 개체입니다.
ConcreteObserver Observer를 구체화한 개체입니다.

특징

1개 이상의 ObserverSubject를 감시할 수 있습니다.

예제

다음은 MyDoc에 데이터가 추가되었을때 데이터의 합계와 평균을 출력하는 예입니다. 합계를 출력하는 SumObserver와 평균을 출력하는 AverObserverMyDoc을 감시하며, 변경 사항이 있으면, MyDoc에서 값을 가져옵니다.

  1. #1 : IObserverSubject를 감시합니다.
  2. #2 : SubjectAttach()Detach()IObserver를 등록/해제 할 수 있으며, 변경 사항이 있으면, 모든 IObserverUpdate()를 호출합니다.
  3. #3 : MyDoc에서 데이터가 추가되면, 모든 Observer에 통지합니다.
  4. #4 : SumObserverAverObserverMyDoc을 감시하며, 변경 사항이 있으면 합계와 평균을 갱신합니다.
  5. #5 : AverObserver감시를 해제한 후 데이터를 추가하면, 평균값은 갱신되지 않습니다.
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// ----
// #1. Subject를 감시합니다.
// ----
class IObserver {
protected:
    IObserver() = default; // 인터페이스여서 상속해서만 사용하도록 protected
    ~IObserver() = default; // 인터페이스여서 다형 소멸을 하지 않으므로 protected non-virtual
private:
    IObserver(const IObserver&) = delete;
    IObserver(IObserver&&) = delete;
    IObserver& operator =(const IObserver&) = delete;
    IObserver& operator =(IObserver&&) = delete;   
public:
    // Subject가 수정될때 호출됩니다.
    virtual void Update() = 0;
};
// ----
// #2. Attach()와 Detach()로 IObserver를 등록/해제 할 수 있으며, 변경 사항이 있으면, 모든 IObserver의 Update()를 호출합니다.
// ----
class Subject {
private:
    std::list<IObserver*> m_Observers;
protected:
    Subject() = default; // 상속해서만 사용하도록 protected
    ~Subject() = default; // 다형 소멸을 하지 않으므로 protected non-virtual
private:
    Subject(const Subject&) = delete;
    Subject(Subject&&) = delete;
    Subject& operator =(const Subject&) = delete;
    Subject& operator =(Subject&&) = delete;   
public:
    // Observer를 등록합니다.
    void Attach(IObserver* observer) {
        assert(observer);

        m_Observers.push_back(observer);
    }
    // Observer를 해제합니다.
    void Detach(IObserver* observer) {
        assert(observer);

        std::list<IObserver*>::iterator itr{std::find(m_Observers.begin(), m_Observers.end(), observer)};
        
        if (itr != m_Observers.end()) {
            m_Observers.erase(itr);
        }
    }

    // #2. 변경 사항이 있으면, 모든 Observer의 Update()를 호출합니다.
    void Notify() {
        for(auto observer : m_Observers) {
            observer->Update();
        }
    }
};
// ----
// #3. 데이터가 추가되면 모든 Observer에 통지합니다.
// ----
class MyDoc : public Subject {
    std::vector<int> m_Data;
public:
    MyDoc() = default;
    ~MyDoc() = default;
private:
    MyDoc(const MyDoc&) = delete;
    MyDoc(MyDoc&&) = delete;
    MyDoc& operator =(const MyDoc&) = delete;
    MyDoc& operator =(MyDoc&&) = delete;  
public:
    void Add(int val) {
        m_Data.push_back(val);
        Notify(); // #3
    }
    int GetSum() const {
        return std::accumulate(m_Data.begin(), m_Data.end(), 0);
    }
    int GetAver() const {
        assert(0 < m_Data.size());
        
        return GetSum() / m_Data.size();
    } 
};

// ----
// #4. MyDoc을 감시하며, 변경 사항이 있으면, Sum값을 업데이트 합니다.
// ----
class SumObserver : public IObserver {
    MyDoc& m_MyDoc;
    int m_Val;
public:
    explicit SumObserver(MyDoc& myDoc) : m_MyDoc{myDoc} {}

    virtual void Update() override {
        m_Val = m_MyDoc.GetSum(); // #4
    }

    int GetVal() const {return m_Val;}
};

// ----   
// #4. MyDoc을 감시하며, 변경 사항이 있으면, Aver값을 업데이트 합니다.
// ----
class AverObserver : public IObserver {
    MyDoc& m_MyDoc;
    int m_Val;
public:
    explicit AverObserver(MyDoc& myDoc) : m_MyDoc{myDoc} {}

    virtual void Update() override {
        m_Val = m_MyDoc.GetAver(); // #4
    }

    int GetVal() const {return m_Val;}
};

// ----
// 테스트 코드
// ----  
MyDoc myDoc;

SumObserver sumObserver{myDoc};
AverObserver averObserver{myDoc};

myDoc.Attach(&sumObserver); // #2. myDoc을 감시합니다.
myDoc.Attach(&averObserver);

myDoc.Add(1);
myDoc.Add(2);
myDoc.Add(3);

EXPECT_TRUE(sumObserver.GetVal() == 1 + 2 + 3);
EXPECT_TRUE(averObserver.GetVal() == (1 + 2 + 3) / 3);

myDoc.Detach(&averObserver); // #2. averObserver 감시를 해제 합니다.

myDoc.Add(10); // #5

EXPECT_TRUE(sumObserver.GetVal() == 1 + 2 + 3 + 10); // sum은 계속 감시중이어서 값이 갱신됩니다.
EXPECT_TRUE(averObserver.GetVal() == (1 + 2 + 3) / 3); // #5. aver는 감시를 해제하여 값이 갱신되지 않습니다.

댓글남기기