觀察者模式
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
以下是模式的摘要:
-
物件(
DigitalClock
或AnalogClock
物件)使用 Subject 介面(Attach()
或Detach()
)作為觀察者訂閱(註冊)或取消訂閱(刪除)自己作為觀察者(subject.Attach(*this);
,subject.Detach(*this);
。 -
每個科目都可以有很多觀察者(
vector<Observer*> observers;
)。 -
所有觀察者都需要實現 Observer 介面。這個介面只有一個方法
Update()
,當 Subject 的狀態改變時被呼叫(Update(Subject &)
) -
除了
Attach()
和Detach()
方法之外,具體主題還實現了Notify()
方法,該方法用於在狀態發生變化時更新所有當前觀察者。但在這種情況下,所有這些都是在父類Subject
(Subject::Attach (Observer&)
,void Subject::Detach(Observer&)
和void Subject::Notify()
。 -
Concrete 物件還可以具有設定和獲取其狀態的方法。
-
具體觀察者可以是實現 Observer 介面的任何類。每個觀察者訂閱(註冊)具有接收更新的具體主題(
subject.Attach(*this);
)。 -
觀察者模式的兩個物件是鬆散耦合的,它們可以相互作用但彼此知之甚少。
變異:
訊號和插槽
訊號和槽是 Qt 中引入的一種語言結構,它可以很容易地實現 Observer 模式,同時避免樣板程式碼。概念是控制元件(也稱為小部件)可以傳送包含事件資訊的訊號,這些資訊可以使用稱為插槽的特殊功能由其他控制元件接收。Qt 中的槽必須是宣告為此類的類成員。訊號/插槽系統非常適合圖形使用者介面的設計方式。類似地,訊號/槽系統可用於非同步 I / O(包括套接字,管道,序列裝置等)事件通知或將超時事件與適當的物件例項和方法或功能相關聯。不需要編寫註冊/登出/呼叫程式碼,因為 Qt 的元物件編譯器(MOC)自動生成所需的基礎結構。
C#語言也支援類似的結構,儘管術語和語法不同:事件扮演訊號的角色,代理是插槽。另外,委託可以是區域性變數,很像函式指標,而 Qt 中的槽必須是宣告為這樣的類成員。