觀察者模式

Observer Pattern 的目的是定義物件之間的一對多依賴關係,以便當一個物件更改狀態時,將自動通知和更新其所有依賴關係。

主題和觀察者定義了一對多關係。觀察者依賴於主體,使得當主體的狀態改變時,觀察者得到通知。根據通知,觀察者也可以使用新值進行更新。

以下是 Gamma 的設計模式一書中的例子。

#include <iostream>
#include <vector>

class Subject; 

class Observer 
{ 
public:
    virtual ~Observer() = default;
    virtual void Update(Subject&) = 0;
};

class Subject 
{ 
public: 
     virtual ~Subject() = default;
     void Attach(Observer& o) { observers.push_back(&o); }
     void Detach(Observer& o)
     {
         observers.erase(std::remove(observers.begin(), observers.end(), &o));
     }
     void Notify()
     {
         for (auto* o : observers) {
             o->Update(*this);
         }
     }
private:
     std::vector<Observer*> observers; 
};

class ClockTimer : public Subject 
{ 
public:

    void SetTime(int hour, int minute, int second)
    {
        this->hour = hour; 
        this->minute = minute;
        this->second = second;

        Notify(); 
    }

    int GetHour() const { return hour; }
    int GetMinute() const { return minute; }
    int GetSecond() const { return second; }

private: 
    int hour;
    int minute;
    int second;
}; 

class DigitalClock: public Observer 
{ 
public: 
     explicit DigitalClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
     ~DigitalClock() { subject.Detach(*this); }
     void Update(Subject& theChangedSubject) override
     {
         if (&theChangedSubject == &subject) {
             Draw();
         }
     }

     void Draw()
     {
         int hour = subject.GetHour(); 
         int minute = subject.GetMinute(); 
         int second = subject.GetSecond(); 

         std::cout << "Digital time is " << hour << ":" 
                   << minute << ":" 
                   << second << std::endl;           
     }

private:
     ClockTimer& subject;
};

class AnalogClock: public Observer 
{ 
public: 
     explicit AnalogClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
     ~AnalogClock() { subject.Detach(*this); }
     void Update(Subject& theChangedSubject) override
     {
         if (&theChangedSubject == &subject) {
             Draw();
         }
     }
     void Draw()
     {
         int hour = subject.GetHour(); 
         int minute = subject.GetMinute(); 
         int second = subject.GetSecond(); 

         std::cout << "Analog time is " << hour << ":" 
                   << minute << ":" 
                   << second << std::endl; 
     }
private:
     ClockTimer& subject;
};

int main()
{ 
    ClockTimer timer; 

    DigitalClock digitalClock(timer); 
    AnalogClock analogClock(timer); 

    timer.SetTime(14, 41, 36);
}

輸出:

Digital time is 14:41:36
Analog time is 14:41:36

以下是模式的摘要:

  1. 物件(DigitalClockAnalogClock 物件)使用 Subject 介面(Attach()Detach())作為觀察者訂閱(註冊)或取消訂閱(刪除)自己作為觀察者(subject.Attach(*this);subject.Detach(*this);

  2. 每個科目都可以有很多觀察者(vector<Observer*> observers;)。

  3. 所有觀察者都需要實現 Observer 介面。這個介面只有一個方法 Update(),當 Subject 的狀態改變時被呼叫(Update(Subject &)

  4. 除了 Attach()Detach() 方法之外,具體主題還實現了 Notify() 方法,該方法用於在狀態發生變化時更新所有當前觀察者。但在這種情況下,所有這些都是在父類 SubjectSubject::Attach (Observer&)void Subject::Detach(Observer&)void Subject::Notify()

  5. Concrete 物件還可以具有設定和獲取其狀態的方法。

  6. 具體觀察者可以是實現 Observer 介面的任何類。每個觀察者訂閱(註冊)具有接收更新的具體主題(subject.Attach(*this);)。

  7. 觀察者模式的兩個物件是鬆散耦合的,它們可以相互作用但彼此知之甚少。

變異:

訊號和插槽

訊號和槽是 Qt 中引入的一種語言結構,它可以很容易地實現 Observer 模式,同時避免樣板程式碼。概念是控制元件(也稱為小部件)可以傳送包含事件資訊的訊號,這些資訊可以使用稱為插槽的特殊功能由其他控制元件接收。Qt 中的槽必須是宣告為此類的類成員。訊號/插槽系統非常適合圖形使用者介面的設計方式。類似地,訊號/槽系統可用於非同步 I / O(包括套接字,管道,序列裝置等)事件通知或將超時事件與適當的物件例項和方法或功能相關聯。不需要編寫註冊/登出/呼叫程式碼,因為 Qt 的元物件編譯器(MOC)自動生成所需的基礎結構。

C#語言也支援類似的結構,儘管術語和語法不同:事件扮演訊號的角色,代理是插槽。另外,委託可以是區域性變數,很像函式指標,而 Qt 中的槽必須是宣告為這樣的類成員。