HeadFirst設計模式之觀察者模式(C++實現)
觀察則模式
1. 面向物件原則
- 封裝變化:找到應用中可能變化之處,把它們獨立出來,不要和那些不需要變化的程式碼混在一起。
把會變化的部分取出並封裝起來,以便以後可以輕易地改動或擴充此部分,而不影響不需要變化的其他部分。 - 針對介面(Interface)程式設計,而不是針對實現(implenments)程式設計。
- 多用組合,少用繼承。
如同書上所說,鴨子的行為不是(IS-A)繼承extends而來的,而是通過各種(HAS-A)介面類FlyBehavior和QuackBehavior組合而來的。
這樣做的好處有:
- 使用組合具有更大的彈性,可以將演算法簇封裝成類
- 只要組合的物件符合正確的介面標準,就可以在執行時動態地改變行為
為互動物件之間的鬆耦合設計而努力。
這樣的好處有:- 當兩個物件之間鬆耦合,他們依然可以(通過介面)互動,但是不太清楚彼此實現的細節。
- 當新型別的觀察者出現時,主題的程式碼不需要修改。所要做的就是在新型別裡實現此觀察者的介面,然後註冊為觀察者即可(使用一個Subject的指標指向實現的具體的主題)。
- 改變主題或觀察者其中一方,並不會影響另一方。
2. 觀察者模式
- 定義了物件之間的一對多依賴,這樣一來,當一個物件改變狀態時,它的所有依賴者都會收到通知並自動更新。
3. 設計模式之觀察者模式的C++實現
3.1建立Subject介面類、Observer介面類、DisplayElement介面類
- 我們使用
多重繼承
的方式來繼承觀察者介面類和用於顯示的介面類。
(抽象基類)介面類Subject.h程式碼:
#pragma once
#ifndef SUBJECT_H
#define SUBJECT_H
#include <memory>
class Observer;//前置宣告
//定義一個主題Subject的抽象介面類
class Subject {
public:
Subject();
virtual ~Subject();
virtual void registerObserver(Observer &rhs) = 0;
virtual void removeObserver(Observer &rhs)= 0;
virtual void notifyObserver() = 0;
};
#endif // !SUBJECT_H
這裡我們的介面函式的引數為
Observer &rhs
,而不是const Observer &rhs
,這是因為其接受的實參不一定是Observer類,而是具體的實現類型別,如CurrentConditionDisplay、StatisticDisplay、ForcastDisplay
。同時複習一下:
const引用可以繫結到非const物件上,但是const物件一樣要繫結到const引用上
。
介面類Subject.cpp程式碼:
#include "Subject.h"
Subject::Subject() {
}
Subject::~Subject() {
}
(抽象基類)介面類Observer.h程式碼:
#pragma once
#ifndef OBSERVER_H
#define OBSERVER_H
//則是一個觀察者Observer的抽象介面類
class Observer {
public:
Observer();
virtual ~Observer();
virtual void update(float temp, float humidity, float pressure) = 0;
//virtual void display() = 0;
};
#endif // !OBSERVER_H
介面類Observer.cpp程式碼:
#include "Observer.h"
Observer::Observer() {
}
Observer::~Observer() {
}
(抽象基類)介面類Observer.h程式碼:
#pragma once
#ifndef DISPLAYELEMENT_H
#define DISPLAYELEMENT_H
//這是一個用於顯示的抽象介面類
class DisplayElement {
public:
DisplayElement();
~DisplayElement();
virtual void display() = 0;
};
#endif // !DISPLAYELEMENT_H
介面類Observer.cpp程式碼:
#include "DisplayElement.h"
DisplayElement::DisplayElement() {
}
DisplayElement::~DisplayElement() {
}
3.2建立Subject介面類的具體實現類WeatherDate
由於主題介面類的具體實現類WeatherDate需要通則多個觀察者,我們使用一個
vector<shared_ptr<Observer>> vpOberbers
來儲存一組指向Observer類的智慧指標。這是這個觀察者模式的重點與難點。這裡是不能用
vector<Observer> vOberbers
來儲存Observer物件的,因為Observer是抽象類,不是例項化,所以我們只能用指標,(為什麼不用引用呢?因為引用一旦綁定了物件之後就不能更改,而且需要定義時初始化。),這裡我們為了鍛鍊智慧指標的用法(同時更安全),我們採用智慧指標。
具體實現類WeatherDate.h程式碼:
#pragma once
#ifndef WEATHERDATA_H
#define WEATHERDATA_H
#include <iostream>
#include <vector>
#include "Subject.h"
#include "Observer.h"
class WeatherDate :
public Subject {
public:
WeatherDate();
~WeatherDate();
//自定義的建構函式
WeatherDate(float temp, float hmdy, float pse, std::vector<std::shared_ptr<Observer>> voberbers) :
temperature(temp), humidity(hmdy), pressure(pse), vpOberbers(voberbers) {}
//自定義的建構函式
WeatherDate(std::vector<std::shared_ptr<Observer>> voberbers) :
vpOberbers(voberbers) {}
void registerObserver(Observer &rhs) override;//override明確宣告要覆蓋基類的virtual函式
void removeObserver(Observer &rhs) override;//override明確宣告要覆蓋基類的virtual函式
void notifyObserver()override;//override明確宣告要覆蓋基類的virtual函式
void measurementsChanged();
void setMeasurements(float temperature, float humidity, float pressure);
private:
float temperature;
float humidity;
float pressure;
//智慧指標陣列(陣列中的每一個成員都是一個指向Observer物件的智慧指標)
std::vector<std::shared_ptr<Observer>> vpOberbers;
};
#endif // !WEATHERDATA_H
具體實現類WeatherDate.cpp程式碼:
#include "WeatherDate.h"
WeatherDate::WeatherDate() {
}
WeatherDate::~WeatherDate() {
}
//void WeatherDate::registerObserver(Observer* rhs) const {
// vOberbers.push_back(rhs);
//}
//
//void WeatherDate::removeObserver(Observer* rhs) const {
// int i = vOberbers.size();
// if (i >= 0) {
// vOberbers.erase(rhs);
// }
//}
void WeatherDate::registerObserver(Observer& rhs) {
//註冊為觀察者
std::shared_ptr<Observer> pObserver(&rhs);//轉換為智慧指標型別
vpOberbers.push_back(pObserver);
}
void WeatherDate::removeObserver(Observer& rhs) {
//取消訂閱
std::shared_ptr<Observer> pObserver(&rhs);//轉換為智慧指標型別
if (!vpOberbers.empty()) {
for (auto iter = vpOberbers.begin(); iter != vpOberbers.end(); ++iter) {
if (*iter == pObserver)
//注意erase方法只能刪除迭代器
vpOberbers.erase(iter);
}
}
}
void WeatherDate::notifyObserver() {
//通知每一個觀察者
//使用auto遍歷每一個成員,注意這裡要用引用!
for (auto &i : vpOberbers) {
i->update(temperature, humidity, pressure);
}
}
//void WeatherDate::notifyObserver(){
// //使用auto遍歷每一個成員
// //for (auto &i : vOberbers) {
// // i->update(temperature, humidity, pressure);
// //}
//}
void WeatherDate::measurementsChanged() {
notifyObserver();
}
void WeatherDate::setMeasurements(float temperature, float humidity, float pressure) {
this->temperature = temperature;
this->humidity = humidity;
this->pressure = pressure;
measurementsChanged();
}
- 注意,我們在for迴圈中還是用了C++11的auto關鍵字,這樣具有自動型別推倒,更安全,更簡潔。
3.2建立Observer介面類的具體實現類CurrentConditionDisplay、StatisticsDisplay、ForecastDisplay
實現類CurrentConditionDisplay.h程式碼:
#pragma once
#ifndef CURRENTDISPLAY_H
#define CURRENTDISPLAY_H
#include "Subject.h"
#include "Observer.h"
#include "DisplayElement.h"
//具體的實現類CurrentConditionDisplay使用了多重繼承
//繼承了Observer和DisplayElement
//注意,當使用多重繼承時,被繼承的基類不應在基類中放置資料成員
class CurrentConditionDisplay :
public Observer,
public DisplayElement{
public:
CurrentConditionDisplay();
//自定義的建構函式
////****************資料成員為智慧指標時用此函式**************//
//CurrentConditionDisplay(std::shared_ptr<Subject> &wd){
// this->weatherData = wd;//這裡的this->可以省略
// //由於Subject介面類的registerObserver函式型別為void registerObserver(Observer &rhs)
// //因此具體的實現類物件weatherDate呼叫時傳入this指標是不行的,需要用引用指向物件值*this
// //引用必須初始化,指向具體的物件,而不像指標初始化指向物件的地址
// CurrentConditionDisplay& rthis = *this;
// //註冊為觀察者。這裡出現了動態繫結,靜態型別為Observer &rhs,動態型別為CurrentConditionDisplay& rthis
// weatherData->registerObserver(rthis);
//}
//****************資料成員為普通指標時用此函式**************//
CurrentConditionDisplay(Subject* wd) {
this->weatherData = wd;//這裡的this->可以省略
//由於Subject介面類的registerObserver函式型別為void registerObserver(Observer &rhs)
//因此具體的實現類物件weatherDate呼叫時傳入this指標是不行的,需要用引用指向物件值*this
//引用必須初始化,指向具體的物件,而不像指標初始化指向物件的地址
CurrentConditionDisplay& rthis = *this;
//註冊為觀察者。這裡出現了動態繫結,靜態型別為Observer &rhs,動態型別為CurrentConditionDisplay& rthis
weatherData->registerObserver(rthis);
}
~CurrentConditionDisplay();
void update(float temp, float humidity, float pressure) override;//從Observer介面類中繼承過來
void display() override;//從DisplayElement介面類中繼承過來
private:
float temperature;
float humidity;
float pressure;
Subject* weatherData;//這個資料成員為指向Subject的指標,是否能用智慧指標替代?
//std::shared_ptr<Subject> weatherData;//智慧指標替代方案
};
#endif // ! CURRENTDISPLAY_H
- 這裡唯一需要注意的是
CurrentConditionDisplay& rthis = *this;
和weatherData->registerObserver(rthis);
這兩句程式碼的作用:將物件轉換為引用型別,實參型別為CurrentConditionDisplay&
,而函式的形參型別為Observer&
實現類CurrentConditionDisplay.cpp程式碼:
#include "CurrentConditionDisplay.h"
#include <iostream>
CurrentConditionDisplay::CurrentConditionDisplay() {
}
CurrentConditionDisplay::~CurrentConditionDisplay() {
//智慧指標不需要手動delete
/*delete weatherData;*/
}
void CurrentConditionDisplay::update(float temp, float humidity, float pressure) {
this->temperature = temp;
this->humidity = humidity;
//this->pressure = pressure;
display();
}
void CurrentConditionDisplay::display() {
std::cout << "Current conditions: " <<
temperature << "F degrees and " <<
humidity << "% humidity" << std::endl;
}
實現類StatisticsDisplay.h程式碼:
#pragma once
#ifndef STATISTICSDISPLAY_H
#define STATISTICSDISPLAY_H
#include "Observer.h"
#include "DisplayElement.h"
#include "WeatherDate.h"
//多重繼承,參照CurrentConditionDisplay
//統計當前的平均/最大/最小溫度值
class StatisticsDisplay :
public Observer,
public DisplayElement {
public:
StatisticsDisplay();
//自定義的建構函式
StatisticsDisplay(WeatherDate* wd) : weatherData(wd) {
StatisticsDisplay &rthis = *this;
weatherData->registerObserver(rthis);//註冊為觀察者
}
~StatisticsDisplay();
void update(float temp, float humidity, float pressure) override;//從Observer介面類中繼承過來
void display() override;//從DisplayElement介面類中繼承過來
private:
float maxTemp = 0.0f;//最大溫度
float minTemp = 200;//最小溫度
float tempSum = 0.0f;//溫度總和
int numReadings;//更新次數
//這裡是否可以用Subject指標?
WeatherDate* weatherData;//不好意思,這裡打錯了,應該為WeatherData,懶得改了
};
#endif // !STATISTICSDISPLAY_H
實現類StatisticsDisplay.cpp程式碼:
#include "StatisticsDisplay.h"
#include <iostream>
StatisticsDisplay::StatisticsDisplay() {
}
StatisticsDisplay::~StatisticsDisplay() {
}
void StatisticsDisplay::update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
void StatisticsDisplay::display() {
std::cout << "Avg/Max/Min temperature = "
<< (tempSum / numReadings)
<< "/"
<< maxTemp
<< "/"
<< minTemp
<< std::endl;
}
實現類ForecastDisplay.h程式碼:
#pragma once
#ifndef FORCASTDISPLAY_H
#define FORCASTDISPLAY_H
#include "Observer.h"
#include "DisplayElement.h"
#include "WeatherDate.h"
//多重繼承,參照CurrentConditionDisplay
//通過氣壓預測未來的天氣
class ForecastDisplay :
public Observer,
public DisplayElement {
public:
ForecastDisplay();
//自定義的建構函式
ForecastDisplay(WeatherDate* wd){
this->weatherData = wd;
ForecastDisplay &rthis = *this;
weatherData->registerObserver(rthis);//註冊為觀察者
}
~ForecastDisplay();
void update(float temp, float humidity, float pressure) override;//從Observer介面類中繼承過來
void display() override;//從DisplayElement介面類中繼承過來
private:
float currentPressure = 29.92f;
float lastPressure;
WeatherDate* weatherData;
};
#endif // !FORCASTDISPLAY_H
實現類ForecastDisplay.cpp程式碼:
#include "ForecastDisplay.h"
#include <iostream>
ForecastDisplay::ForecastDisplay() {
}
ForecastDisplay::~ForecastDisplay() {
}
void ForecastDisplay::update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
void ForecastDisplay::display() {
if (currentPressure > lastPressure) {
std::cout << "Improving weather on the way!" << std:: endl;
}
else if (currentPressure == lastPressure) {
std::cout << "More of the same" << std::endl;
}
else if (currentPressure < lastPressure) {
std::cout << "Watch out for cooler, rainy weather" << std::endl;
}
}
執行結果截圖: