观察者模式

            观察者模式(Observer):定义了对象之间的一对多关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

            实现观察者模式的方法有多种,但是以包含Subject与Observer接口的类设计的做法最常见,下面看看观察者模式的类图:

                         

           主题(Subject)是真正拥有数据的人,观察者则是主题的依赖者,在主题数据变化时接收通知并更新。这样比起让许多对象控制同一份数据来,可以得到更干净的OO设计。

           主题与观察者之间是松耦合的,它们可以交互,但不知道彼此的细节。比如对于观察者,主题只知道观察者实现了某个接口,主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。任何时候我们都可以增加新的观察者,因为主题唯一依赖的东西是一个实现Observer接口的对象列表;同样的,我们也可以在任何时候删除某些观察者。因为松耦合的关系,改变主题或观察者其中一方,并不会影响另一方。松耦合的设计能让我们建立有弹性的OO系统以应对变化,因为对象之间的互相依赖降到了最低。

          下面就以经典的气象监测问题为例,说说观察者模式的应用:

           

      此气象站系统只包括三个部分:气象站、WeatherData对象、布告板。

      工作的流程:WeatherData对象从气象站获取最新的测量数据(温度、湿度、气压),并及时更新到三个布告板(显示装置)上。

      假设从气象站获取数据的方法已经实现好了,那么我们只需要考虑如何将新的数据更新到三个布告板上,而且要尽量实现系统可拓展,让其他开发人员可以定制布告板,用户可以任意的添加或删除布告板,那么我们如何建立这个系统呢?

      当然是使用观察者模式,这里的WeatherData类正是观察者模式中的“一”,即主题;而布告板就是“多”,即观察者;这样就建立起了一对多的依赖关系。WeatherData对象是真正拥有数据的一方,包括温度、湿度、气压,当这些值改变时,必须通知所有的布告板,好让它们各自做出处理。在这里,布告板作为Observer为了获取数据,必须先向WeatherData对象注册,一旦WeatherData知道有某个布告板的存在,就会适时地调用布告板的某个公共的接口(例如. update)来告诉布告板观测值是多少。由于update()方法是所有布告板公共的接口,所以需要在布告板的基类(Java中说接口)中定义。下面是设计图:


  • WeatherData实现主题(Subject)接口
  • 布告板实现观察者(Observer)接口,这样主题在需要通知观察者时,就有了一个共同的接口
  • 同时还为布告板建立一个共同的接口DisplayElement,用于实现display()方法
  • 每个布告板中应该声明一个Subject接口类对象

C++实现:

// Subject接口类
#pragma once
#include "Observer.h"

class Subject   
{
public:
	virtual void registerObserver(Observer* o) = 0;
	virtual void removeObserver(Observer* o) = 0;
	virtual void notifyObserver() = 0;
};

// Observer接口类
#pragma once
class Observer  
{
public:
	virtual void update(float temp, float humidity, float pressure) = 0;
};

// 接口类 用于display
#pragma once
class DisplayElement
{
public:
	virtual void display() = 0;
};

// WeatherData实现Subject接口
#pragma once
#include "Observer.h"
#include "Subject.h"
#include <list>

class WeatherData : public Subject
{
private:
	mutable std::list<Observer*> observers;
	float temperature;
	float humidity;
	float pressure;

public:
	WeatherData(void);
	~WeatherData(void);
	void registerObserver(Observer* o);
	void removeObserver(Observer* o);
	void notifyObserver();
	void measurementsChanged();
	void setMeasurements(float temperature, float humidity, float pressure);
	// 以下方法用于从气象台获取数据,这里不予考虑
    float getTemperature();
	float getHumidity();
	float getPressure();
};


// WeatherData.cpp
#include "WeatherData.h"
using namespace std;

void WeatherData::registerObserver( Observer* o )
{
	observers.push_back(o);
}

void WeatherData::removeObserver( Observer* o )
{
	observers.remove(o);
}

void WeatherData::notifyObserver()
{
	for (list<Observer*>::iterator iterator=observers.begin(); observers.end()!=iterator; ++iterator)
	{
		Observer* observer = *iterator;
		observer->update(temperature, humidity, pressure);
	}
}

void WeatherData::measurementsChanged()
{
	notifyObserver();
}

void WeatherData::setMeasurements( float temperature, float humidity, float pressure )
{
	this->temperature = temperature;
	this->humidity = humidity;
	this->pressure = pressure;
	measurementsChanged();
}

float WeatherData::getTemperature()
{
	return temperature;
}

float WeatherData::getHumidity()
{
	return humidity;
}

float WeatherData::getPressure()
{
	return pressure;
}

// “目前状况”布告板 实现观察者接口
#pragma once
#include "WeatherData.h"
#include "Observer.h"
#include "DisplayElement.h"

class CurrentConditionsDisplay : public Observer, public DisplayElement
{
private:
	float temperature;
	float humidity;
	Subject* weatherData;

public:
	CurrentConditionsDisplay(Subject* weatherData);
	~CurrentConditionsDisplay(void);
	void update(float temp, float humidity, float pressure);
	void display();
};

// CurrentConditionsDisplay.cpp
#include "CurrentConditionsDisplay.h"
#include <iostream>
using namespace std;


CurrentConditionsDisplay::CurrentConditionsDisplay( Subject* weatherData )
{
	this->weatherData = weatherData;    // 构造器中注册为观察者
	weatherData->registerObserver(this);
}

CurrentConditionsDisplay::~CurrentConditionsDisplay(void)
{
	weatherData->removeObserver(this);
}

void CurrentConditionsDisplay::update( float temp, float humidity, float pressure )
{
	this->temperature = temp;
	this->humidity = humidity;
	display();
}

void CurrentConditionsDisplay::display()
{
	cout.setf( ios::showpoint );
	cout.precision(3);
	cout << "Current conditions: "	<< temperature;
	cout << " F degrees and " << humidity;
	cout << "% humidity" << endl;
}

// 数据统计布告板
#pragma once
#include "WeatherData.h"
#include "Observer.h"
#include "DisplayElement.h"

class StatisticsDisplay : public Observer, public DisplayElement
{
private:
	Subject* weatherData;
    float maxTemp;
	float minTemp;
	float tempSum;
	int numReadings;

public:
	StatisticsDisplay(Subject* weatherData);
	~StatisticsDisplay(void);
	void update(float temp, float humidity, float pressure);
	void display();
};


// StatisticsDisplay.cpp
#include "StatisticsDisplay.h"
#include <iostream>
using namespace std;

StatisticsDisplay::StatisticsDisplay(Subject* weatherData)
{
	maxTemp = 0.0;      // 记录最高温
	minTemp = 200.0F;   // 记录最低温
	tempSum = 0.0;      // 温度和,用于计算平均温度
	numReadings = 0;    // 次数,用于计算平均温度
	this->weatherData = weatherData;    // 构造器中注册为观察者
	weatherData->registerObserver(this);
}


StatisticsDisplay::~StatisticsDisplay(void)
{
	weatherData->removeObserver(this);
}

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()
{
	cout.setf( ios::showpoint );
	cout.precision(3);
	cout << "Avg/Max/Min temperature = " << ( tempSum / numReadings );
	cout << "/" << maxTemp << "/" << minTemp << endl;
}

// 天气预报布告板
#pragma once
#include "WeatherData.h"
#include "Observer.h"
#include "DisplayElement.h"

class ForecastDisplay : public Observer, public DisplayElement
{
private:
	float currentPressure;
	float lastPressure;
	Subject* weatherData;

public:
	ForecastDisplay(Subject* weatherData);
	~ForecastDisplay(void);
	void update(float temp, float humidity, float pressure);
	void display();
};


// ForecastDisplay.cpp
#include "ForecastDisplay.h"
#include <iostream>
using namespace std;

ForecastDisplay::ForecastDisplay(Subject* weatherData)
{
	currentPressure = 29.92F;  // 假设现在的
	lastPressure = 0.0;
	this->weatherData = weatherData;    // 构造器中注册为观察者
	weatherData->registerObserver(this);
}


ForecastDisplay::~ForecastDisplay(void)
{
	weatherData->removeObserver(this);
}

void ForecastDisplay::update( float temp, float humidity, float pressure )
{
	lastPressure = currentPressure;
	currentPressure = pressure;
	display();
}

void ForecastDisplay::display()
{
	cout << "Forecast: ";
	if( currentPressure > lastPressure ) {
		cout << "Improving weather on the way!";
	} else if( currentPressure == lastPressure ) {
		cout << "More of the same";
	} else if( currentPressure < lastPressure ) {
		cout << "Watch out for cooler, rainy weather";
	}
	cout << endl;

}

// 程序入口WeatherStation.cpp
#include "WeatherData.h"
#include "CurrentConditionsDisplay.h"
#include "ForecastDisplay.h"
#include "StatisticsDisplay.h"

int main(){
	WeatherData* weatherData = new WeatherData;

	CurrentConditionsDisplay* cu = new CurrentConditionsDisplay(weatherData);
	StatisticsDisplay* st = new StatisticsDisplay(weatherData);
	ForecastDisplay* fo = new ForecastDisplay(weatherData);
    // 更新数据,这里手动模拟
	weatherData->setMeasurements( 80, 65, 30.4f );
	weatherData->setMeasurements( 82, 70, 29.2f );
	weatherData->setMeasurements( 78, 90, 29.2f );

	getchar();
	return 0;
}

运行结果:





个人站点:http://songlee24.github.io/

posted @ 2014-03-12 15:32  神奕  阅读(210)  评论(0编辑  收藏  举报