观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,使得多个观察者对象同时监听某一个主题对象。当该主题对象的状态发生变化时,它会通知所有观察者对象,使它们能够自动更新。
观察者模式的主要特点包括:
- 解耦:观察者模式将观察者与被观察者解耦,使得它们可以独立变化。
- 动态订阅:观察者可以在运行时动态地订阅或取消订阅被观察者的通知。
- 广播通信:被观察者向所有注册的观察者广播通知,而不需要知道具体有哪些观察者。
示例:使用观察者模式实现一个简单的天气预报系统
我们将实现一个简单的天气预报系统,包含一个天气数据的主题和两个观察者:一个显示当前天气情况,另一个显示天气统计信息。
接口和具体类
首先,定义观察者和主题的接口:
#include <iostream>
#include <vector>
#include <memory>
// 观察者接口
class Observer {
public:
virtual ~Observer() = default;
virtual void update(float temperature, float humidity, float pressure) = 0;
};
// 主题接口
class Subject {
public:
virtual ~Subject() = default;
virtual void registerObserver(std::shared_ptr<Observer> observer) = 0;
virtual void removeObserver(std::shared_ptr<Observer> observer) = 0;
virtual void notifyObservers() = 0;
};
具体主题类
然后,创建具体的主题类:
class WeatherData : public Subject {
public:
void registerObserver(std::shared_ptr<Observer> observer) override {
observers.push_back(observer);
}
void removeObserver(std::shared_ptr<Observer> observer) override {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notifyObservers() override {
for (const auto& observer : observers) {
observer->update(temperature, humidity, pressure);
}
}
void setMeasurements(float temp, float hum, float pres) {
temperature = temp;
humidity = hum;
pressure = pres;
notifyObservers();
}
private:
std::vector<std::shared_ptr<Observer>> observers;
float temperature;
float humidity;
float pressure;
};
具体观察者类
接着,创建具体的观察者类:
class CurrentConditionsDisplay : public Observer {
public:
void update(float temperature, float humidity, float pressure) override {
this->temperature = temperature;
this->humidity = humidity;
display();
}
void display() {
std::cout << "Current conditions: " << temperature
<< "C degrees and " << humidity << "% humidity" << std::endl;
}
private:
float temperature;
float humidity;
};
class StatisticsDisplay : public Observer {
public:
void update(float temperature, float humidity, float pressure) override {
tempSum += temperature;
numReadings++;
if (temperature > maxTemp) {
maxTemp = temperature;
}
if (temperature < minTemp) {
minTemp = temperature;
}
display();
}
void display() {
std::cout << "Avg/Max/Min temperature = " << (tempSum / numReadings)
<< "/" << maxTemp << "/" << minTemp << std::endl;
}
private:
float maxTemp = 0.0f;
float minTemp = 200.0f;
float tempSum = 0.0f;
int numReadings = 0;
};
主函数测试
最后,在主函数中创建主题和观察者对象,并模拟天气数据的变化:
int main() {
std::shared_ptr<WeatherData> weatherData = std::make_shared<WeatherData>();
std::shared_ptr<CurrentConditionsDisplay> currentDisplay = std::make_shared<CurrentConditionsDisplay>();
std::shared_ptr<StatisticsDisplay> statisticsDisplay = std::make_shared<StatisticsDisplay>();
weatherData->registerObserver(currentDisplay);
weatherData->registerObserver(statisticsDisplay);
weatherData->setMeasurements(25.0f, 65.0f, 1013.0f);
weatherData->setMeasurements(27.0f, 70.0f, 1012.0f);
weatherData->setMeasurements(26.0f, 90.0f, 1011.0f);
return 0;
}
运行结果
当运行上述代码时,输出将如下:
Current conditions: 25C degrees and 65% humidity
Avg/Max/Min temperature = 25/25/25
Current conditions: 27C degrees and 70% humidity
Avg/Max/Min temperature = 26/27/25
Current conditions: 26C degrees and 90% humidity
Avg/Max/Min temperature = 26/27/25
解释
- Observer:这是一个观察者接口,定义了一个
update
方法,用于更新观察者的状态。 - Subject:这是一个主题接口,定义了注册、移除和通知观察者的方法。
- WeatherData:这是具体的主题类,维护观察者列表,并实现了注册、移除和通知观察者的方法。当天气数据变化时,通过
setMeasurements
方法更新数据并通知观察者。 - CurrentConditionsDisplay和StatisticsDisplay:这两个类是具体的观察者,实现了
update
方法,分别显示当前天气情况和天气统计信息。 - main:创建
WeatherData
、CurrentConditionsDisplay
和StatisticsDisplay
对象,注册观察者,并模拟天气数据的变化。
总结
观察者模式通过定义一对多的依赖关系,使得多个观察者可以监听一个主题对象,当主题对象状态发生变化时,自动通知所有观察者进行更新。它将观察者与被观察者解耦,提供了一个灵活的机制来实现对象间的通信,适用于需要实时更新的场景。
优化1
update
方法的参数数量不能够变化,假如只需要一个风速参数。可以使用一个通用的数据结构来传递参数,比如使用一个结构体或者一个容器(如std::vector
或std::map
)来传递参数。这种方法可以灵活地处理不同数量和类型的参数,而不需要修改方法签名。
示例:使用结构体传递参数
我们可以定义一个结构体来包含天气数据,然后在update
方法中传递这个结构体。
定义数据结构和接口
首先,定义一个包含天气数据的结构体和观察者接口:
// 天气数据结构体
struct WeatherData {
float temperature;
float humidity;
float pressure;
};
// 观察者接口
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const WeatherData& data) = 0;
};
通过使用结构体传递参数,可以在不修改方法签名的情况下灵活地传递不同数量和类型的参数。这种方法使观察者模式更加灵活,适用于参数数量和类型可能变化的场景。结构体的使用不仅简化了代码,还增强了代码的可读性和可维护性。
优化2
可以观察到Observer的update仍然需要参数限制,是否参数可以为空?
可以使用“拉取(Pull)”模型来实现观察者模式,其中观察者主动从主题中拉取数据,而不是被动地接收数据。这种方式可以使观察者模式更加灵活,观察者可以根据需要拉取所需的数据,而不是依赖主题推送的数据。
示例:使用拉取模型实现观察者模式
接口和具体类
首先,定义观察者和主题的接口:
#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>
// 观察者接口
class Observer {
public:
virtual ~Observer() = default;
virtual void update() = 0;
};
// 主题接口
class Subject {
public:
virtual ~Subject() = default;
virtual void registerObserver(std::shared_ptr<Observer> observer) = 0;
virtual void removeObserver(std::shared_ptr<Observer> observer) = 0;
virtual void notifyObservers() = 0;
virtual float getTemperature() const = 0;
virtual float getHumidity() const = 0;
virtual float getPressure() const = 0;
};
具体主题类
然后,创建具体的主题类:
class WeatherStation : public Subject {
public:
void registerObserver(std::shared_ptr<Observer> observer) override {
observers.push_back(observer);
}
void removeObserver(std::shared_ptr<Observer> observer) override {
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void notifyObservers() override {
for (const auto& observer : observers) {
observer->update();
}
}
void setMeasurements(float temp, float hum, float pres) {
temperature = temp;
humidity = hum;
pressure = pres;
notifyObservers();
}
float getTemperature() const override {
return temperature;
}
float getHumidity() const override {
return humidity;
}
float getPressure() const override {
return pressure;
}
private:
std::vector<std::shared_ptr<Observer>> observers;
float temperature;
float humidity;
float pressure;
};
具体观察者类
接着,创建具体的观察者类:
class CurrentConditionsDisplay : public Observer {
public:
CurrentConditionsDisplay(std::shared_ptr<Subject> weatherStation)
: weatherStation(weatherStation) {}
void update() override {
float temperature = weatherStation->getTemperature();
float humidity = weatherStation->getHumidity();
display(temperature, humidity);
}
void display(float temperature, float humidity) {
std::cout << "Current conditions: " << temperature
<< "C degrees and " << humidity << "% humidity" << std::endl;
}
private:
std::shared_ptr<Subject> weatherStation;
};
class StatisticsDisplay : public Observer {
public:
StatisticsDisplay(std::shared_ptr<Subject> weatherStation)
: weatherStation(weatherStation) {}
void update() override {
float temperature = weatherStation->getTemperature();
tempSum += temperature;
numReadings++;
if (temperature > maxTemp) {
maxTemp = temperature;
}
if (temperature < minTemp) {
minTemp = temperature;
}
display();
}
void display() {
std::cout << "Avg/Max/Min temperature = " << (tempSum / numReadings)
<< "/" << maxTemp << "/" << minTemp << std::endl;
}
private:
std::shared_ptr<Subject> weatherStation;
float maxTemp = 0.0f;
float minTemp = 200.0f;
float tempSum = 0.0f;
int numReadings = 0;
};
主函数测试
最后,在主函数中创建主题和观察者对象,并模拟天气数据的变化:
int main() {
std::shared_ptr<WeatherStation> weatherStation = std::make_shared<WeatherStation>();
std::shared_ptr<CurrentConditionsDisplay> currentDisplay = std::make_shared<CurrentConditionsDisplay>(weatherStation);
std::shared_ptr<StatisticsDisplay> statisticsDisplay = std::make_shared<StatisticsDisplay>(weatherStation);
weatherStation->registerObserver(currentDisplay);
weatherStation->registerObserver(statisticsDisplay);
weatherStation->setMeasurements(25.0f, 65.0f, 1013.0f);
weatherStation->setMeasurements(27.0f, 70.0f, 1012.0f);
weatherStation->setMeasurements(26.0f, 90.0f, 1011.0f);
return 0;
}
运行结果
当运行上述代码时,输出将如下:
Current conditions: 25C degrees and 65% humidity
Avg/Max/Min temperature = 25/25/25
Current conditions: 27C degrees and 70% humidity
Avg/Max/Min temperature = 26/27/25
Current conditions: 26C degrees and 90% humidity
Avg/Max/Min temperature = 26/27/25
解释
- Observer:这是一个观察者接口,定义了一个
update
方法,用于通知观察者进行更新。 - Subject:这是一个主题接口,定义了注册、移除和通知观察者的方法,还包括获取数据的方法(
getTemperature
、getHumidity
和getPressure
)。 - WeatherStation:具体的主题类,实现了注册、移除和通知观察者的方法。它还提供了获取天气数据的方法。
- CurrentConditionsDisplay和StatisticsDisplay:具体的观察者类,实现了
update
方法,在需要时从主题中拉取数据进行显示。 - main:创建
WeatherStation
、CurrentConditionsDisplay
和StatisticsDisplay
对象,注册观察者,并模拟天气数据的变化。
通过使用“拉取(Pull)”模型,观察者可以在被通知时主动从主题中获取所需的数据。这种方式使得观察者模式更加灵活,观察者可以根据自身需求拉取数据,而不是依赖主题推送的数据。这样设计不仅增加了系统的灵活性,还可以减少数据冗余,提升系统性能。