设计模式入门
本系列所有内容参考自《HeadFirst设计模式》。因为书中的代码是采用java语言写的,博主这里用C++语言改写。
这里采用讲故事的方式进行讲解。若有错误之处,非常欢迎大家指导。
设计模式:模式不是代码,而针对设计问题的通用解决方案,被认为是历经验证的OO设计经验。设计模式告诉我们如何组织类和对象以解决某种问题。
如果你输出一个helloworld都想使用设计模式的话,那可能真的就有问题了。
正文
提出问题
我们现在手头有一个气象检测应用。气象站接收湿度感应装置 、温度感应装置 、气压感应装置 的数据,然后我们有一个WeatherData对象,它负责追踪来自气象站的数据,并更新布告板(显示目前天气状况给用户看)。
如果我们要接手这个项目,我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报 。三个布告板如下图所示:
现有的WeatherData类源码如下:
cpp
class WeatherData {
float getTemperature(); //返回温度
float getHumidity(); //返回湿度
float getPressure(); //返回气压
void measurementsChanged()
{
/*一旦气象测量更新,此方法会被调用*/
//我们的代码加在这里
}
};
我们的工作是实现measurementsChanged(),好让它更新目前状况、气象统计、天气预报的显示布告板。
我们目前知道的:WeatherData类有三个方法,可以取得三个测量值;当新的数据来临时,measurementsChanged()方法就会被调用(我们不在乎此方法是如何被调用的,我们只在乎它被调用了);我们需要实现三个使用天气数据的布告板,一旦WeatherData有新的测量,这些布告必须马上更新。
一个我们可能想到的measurementsChanged()实现如下:
cpp
class WeatherData {
// 实例变量声明
void measurementsChanged()
{
// 获取最新的测量值
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
// 调用每个布告板更新显示
currtenConditionsDisplay.update(temp, humidity, pressure); // 目前状况布告板更新
statisticsDisplay.update(temp, humidity, pressure); // 气象统计布告板更新
forecastDisplay.update(temp, humidity, pressure); // 天气预报布告板更新
}
};
但是这与一些软件设计原则发生了矛盾。上面代码中调用每个布告板更新显示函数是针对具体实现编程,会导致我们以后在增加或删除布告板时必须修改程序;三个接口都是update,传入的参数也是一样的,所以看起来更像是一个统一的接口。
那我们该如何解决这个问题呢?观察者模式可以帮助我们很好地解决这个问题。
观察者模式
一个很简单的例子就是杂志订阅 。
假设我们订阅了一款杂志,每当这款杂志更新时,它都会给我们送一份。这就是观察者模式,杂志相当于"主题",我们相当于"观察者",当主题发生改变时,就是通知"观察者"。这里要注意的一点是:主题来增加或删除观察者。 还是杂志订阅这个问题,我们想订阅杂志的时候,杂志出版社便会将我们加到它们的订阅名单里,我们不想订阅杂志时,杂志出版社便会将我们从订阅名单里删除。
观察者模式:观察者模式定义了对象之间的一对多依赖("一个主题"对"多个观察者"),这样一来,当一个对象改变状态时,它的所有依赖者(因为主题是真正拥有数据的人,观察者是主题的依赖者)都会收到通知并自动更新。
实现代码如下:
cpp
#include<iostream>
#include<vector>
using namespace std;
class Observer { // 观察者
public:
virtual void update(float temp, float humidity, float pressure) = 0;
};
class Subject { // 抽象主题
virtual void registerObserver(Observer *o)=0;
virtual void removeObserver(Observer *o)=0;
virtual void notifyObserver()=0;
};
class DisplayElement {
virtual void display()=0;
};
class WeatherData : public Subject // 具象主题
{
private:
vector<Observer*> observers;
float temperature;
float humidity;
float pressure;
public:
void registerObserver(Observer *o) // 注册观察者
{
observers.push_back(o);
}
void removeObserver(Observer *o) // 取消观察者
{
auto it = std::find(observers.begin(), observers.end(), o);
if (it != observers.end())
{
int index = std::distance(observers.begin(), it);
cout << "索引是:" << index << endl;;
observers.erase(observers.begin() + index);
cout << "成功删除元素" << endl;
}
else
{
cout << "未找到元素" << endl;
}
}
void notifyObserver() // 通知观察者
{
for (int i = 0; i < observers.size(); i++)
{
Observer *observer = observers[i];
observer->update(temperature, humidity, pressure);
}
}
void measurementsChanged()
{
notifyObserver(); // 通知观察者
}
void setMeasurements(float temperature, float humidity, float pressure)
{
this->temperature = temperature;
this->humidity = humidity;
this->pressure = pressure;
measurementsChanged();
}
};
class StatisticsDisplay : public Observer, public DisplayElement // 观察者
{
private:
float temperature;
float humidity;
WeatherData *weatherData;
public:
StatisticsDisplay(WeatherData *weather)
{
weatherData = weather;
weatherData->registerObserver(this); //主题注册观察者
}
void remove()
{
weatherData->removeObserver(this); // 主题取消观察者
}
void update(float temperature, float humidity, float pressure)
{
this->temperature = temperature;
this->humidity = humidity;
display();
}
void display()
{
cout << "statisticsDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;
}
};
class ForecastDisplay : public Observer, public DisplayElement // 观察者
{
private:
float temperature;
float humidity;
WeatherData *weatherData;
public:
ForecastDisplay(WeatherData *weather)
{
weatherData = weather;
weatherData->registerObserver(this); //主题注册观察者
}
void remove()
{
weatherData->removeObserver(this); // 主题取消观察者
}
void update(float temperature, float humidity, float pressure)
{
this->temperature = temperature;
this->humidity = humidity;
display();
}
void display()
{
cout << "ForecastDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;
}
};
class CurrentConditionsDisplay : public Observer, public DisplayElement // 观察者
{
private:
float temperature;
float humidity;
WeatherData *weatherData;
public:
CurrentConditionsDisplay(WeatherData *weather)
{
weatherData = weather;
weatherData->registerObserver(this); //主题注册观察者
}
void remove()
{
weatherData->removeObserver(this); // 主题取消观察者
}
void update(float temperature, float humidity, float pressure)
{
this->temperature = temperature;
this->humidity = humidity;
display();
}
void display()
{
cout << "CurrentConditionsDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;
}
};
int main()
{
WeatherData *weatherData = new WeatherData; // 定义一个主题对象即可
CurrentConditionsDisplay currentDisplay(weatherData); // 第一个观察者
StatisticsDisplay statisDisplay(weatherData); // 第二个观察者
ForecastDisplay foreDisplay(weatherData); // 第三个观察者
weatherData->setMeasurements(80, 65, 30.4); // 主题信息发生变更
currentDisplay.remove(); // 该观察者取消对主题的订阅
weatherData->setMeasurements(40, 25, 15.4);
foreDisplay.remove(); // 该观察者取消对主题的订阅
weatherData->setMeasurements(15.5, 26, 34);
return 0;
}
以上就是使用C++实现观察者模式的全部代码。
设计原则
- 找出程序中会变化的方面,然后将其和固定不变的方面相分离。
在观察中模式中,会改变的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。 - 针对接口编程,不针对实现编程。
主题与观察者都是用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。
观察者模式较为重要,在很多软件框架和软件设计中都可以看到它的身影,所以大家可以根据代码仔细体会它的思想。工作的那几个月在公司的软件里看到过观察者模式,但是没有自己动手实现,只是明白它的意思。今天自己动手实现了一下,感悟又深了一些。