觀察者模式詳解——參考《Head First設計模式》
阿新 • • 發佈:2019-02-19
觀察者模式的定義
定義了物件之間的一對多依賴,這樣一來,當一個物件狀態改變時,它的所有依賴者都會受到通知並自動更新。
觀察者模式的分析
本文是以氣象站系統為例說明觀察者模式的,簡單來說就是氣象收集到的資料組成了一個數據中心(也就是觀察者模式中的主題),不同的佈告板(觀察者)實時更新資料中心的資料給使用者,從而顯示不同的天氣預告。(如下分析中的類名請參考具體程式碼)
(1)從設計原則角度講,觀察者模式是具備鬆耦合條件的,鬆耦合是通過兩個基類Subject,和Observer實現的,有了Observer基類,WeatherData不需要知道各個佈告板的具體實現只需保證各個佈告板實現Observer介面,反之亦然。
(2)主題向觀察者“推送資訊” vs 觀察者向主題“請求資訊”。總體來說“請求資訊”的方式要優於“推送資訊”的方式,因為推送資訊是把觀察者感興趣與否的資訊都強制給觀察者,並且假設主題需要增加資訊,此時需要更改所有觀察者的實現(程式碼中的update函式),而“請求資訊”可以解決這兩個問題。
推送資訊方式:
void update(float temperature, float humidity, float pressure) //push all the data to observers. { this->temperature = temperature; this->humidity = humidity; display(); }
請求資訊方式:
void update() //let observers pull data from subject.
{
this->temperature = weatherData->getTemperature();
this->humidity = weatherData->getHumidity();
display();
}
(3)觀察者可以在執行期間訂閱主題或退訂主題,實現靈活的觀察模式。
訂閱主題:
退訂主題:CurrentConditionDisplay(Subject* weatherData) { this->weatherData = weatherData; this->weatherData->registerObserver(this); }
void cancelSubscription()
{
this->weatherData->removeObserver(this);
}
(4)可通過changed標誌位來實現觀察者更新資料的頻次的控制。
void notifyObservers()
{
if(changed)
{
for(vector<Observer*>::iterator iter = observers.begin(); iter != observers.end(); ++iter)
{
//(*iter)->update(temperature, humidity, pressure); //push all the data to observers.
(*iter)->update();
}
}
changed = false;
}
觀察者模式的程式碼
#include <iostream>
#include <vector>
#include <algorithm>
#include <crtdbg.h>
#define CRTDBG_MAP_ALLOC
using namespace std;
class Observer
{
public:
virtual void update(float temperature, float humidity, float pressure) {}; //push all the data to observers.
virtual void update(){}; //let observers pull data from subject.
virtual void display(){};
};
class Subject
{
public:
virtual void registerObserver(Observer* o) = 0;
virtual void removeObserver(Observer* o) = 0;
virtual void notifyObservers() = 0;
virtual float getTemperature(){return 0;};
virtual float getHumidity(){return 0;};
virtual float getPressure(){return 0;};
};
class WeatherData : public Subject
{
private:
vector<Observer*> observers;
float temperature;
float humidity;
float pressure;
char changed;
public:
WeatherData() : temperature(0), humidity(0), pressure(0), changed(false){}
void registerObserver(Observer* o)
{
observers.push_back(o);
}
void removeObserver(Observer* o)
{
vector<Observer*>::iterator iter;
iter = find(observers.begin(),observers.end(),o);
if(iter != observers.end())
{
observers.erase(iter);
}
else
{
cout<<"The observer has not been registed."<<endl;
}
}
void setChanged()
{
changed = true;
}
float getTemperature()
{
return temperature;
}
float getHumidity()
{
return humidity;
}
float getPressure()
{
return pressure;
}
void notifyObservers()
{
if(changed)
{
for(vector<Observer*>::iterator iter = observers.begin(); iter != observers.end(); ++iter)
{
//(*iter)->update(temperature, humidity, pressure); //push all the data to observers.
(*iter)->update();
}
}
changed = false;
}
void setMeasurements(float temperature, float humidity, float pressure)
{
this->temperature = temperature;
this->humidity = humidity;
this->pressure = pressure;
measurementsChanged();
}
void measurementsChanged()
{
setChanged();
notifyObservers();
}
};
class CurrentConditionDisplay : public Observer
{
private:
Subject *weatherData;
float temperature;
float humidity;
public:
CurrentConditionDisplay(Subject* weatherData)
{
this->weatherData = weatherData;
this->weatherData->registerObserver(this);
}
void cancelSubscription()
{
this->weatherData->removeObserver(this);
}
void update(float temperature, float humidity, float pressure) //push all the data to observers.
{
this->temperature = temperature;
this->humidity = humidity;
display();
}
void update() //let observers pull data from subject.
{
this->temperature = weatherData->getTemperature();
this->humidity = weatherData->getHumidity();
display();
}
void display()
{
cout<<"Current conditions: "<<temperature<<"F degrees and "<<humidity<<"% humidity"<<endl;
}
};
class StatisticsDisplay : public Observer
{
private:
Subject *weatherData;
float temperature;
float humidity;
float pressure;
public:
StatisticsDisplay(Subject *weatherData)
{
this->weatherData = weatherData;
this->weatherData->registerObserver(this);
}
void cancelSubscription()
{
this->weatherData->removeObserver(this);
}
void update(float temperature, float humidity, float pressure) //push all the data to observers.
{
this->temperature = temperature;
this->humidity = humidity;
this->pressure = pressure;
display();
}
void update() //let observers pull data from subject.
{
this->temperature = weatherData->getTemperature();
this->humidity = weatherData->getHumidity();
this->pressure = weatherData->getPressure();
display();
}
void display()
{
cout<<"Statistics conditions: "<<temperature/10<<"F degree and "<<humidity/10<<"% humidity "<<pressure/10<<"% pressure "<<endl;
}
};
int main()
{
WeatherData *weatherData = new WeatherData();
CurrentConditionDisplay currentDisplay(weatherData);
StatisticsDisplay statisticsDisplay(weatherData);
weatherData->setMeasurements(80,65,30.4);
currentDisplay.cancelSubscription();
weatherData->setMeasurements(70,64,20.3);
delete weatherData;
_CrtDumpMemoryLeaks();
return 0;
}