1. 程式人生 > >HeadFirst設計模式之觀察者模式(C++實現)

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;
    }
}

執行結果截圖:

這裡寫圖片描述