▷将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了它们的可复用性。
▷在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
▷使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
//伪代码,未严格遵循cpp编码标准
//MainForm1.cpp
//违背 依赖倒置 原则
class MainForm : public Form
{
TextBox* txtFilePath; //文件的全路径
TextBox* txtFileNumber; //分割的文件个数
ProgressBar* progressBar; //进度条
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath, number, progressBar);
splitter.split();
}
};
//伪代码,未严格遵循cpp编码标准
//FileSplitter1.cpp
class FileSplitter
{
string m_filePath; //文件路径
int m_fileNum ber; //文件个数
ProgressBar* m_progressBar; //进度条:分割进展
public:
FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
m_filePath(filePath),
m_fileNumber(fileNumber),
m_progressBar(progressBar){
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
m_progressBar->setValue(progressValue); //更新进度条
}
}
};
2)观察者模式解决方法:
//伪代码,未严格遵循cpp编码标准
//MainForm2.cpp
class MainForm : public Form, public IProgress
{
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
ConsoleNotifier cn;
FileSplitter splitter(filePath, number);
splitter.addIProgress(this); //订阅通知
splitter.addIProgress(&cn); //订阅通知
splitter.split();
splitter.removeIProgress(this);
}
virtual void DoProgress(float value){
progressBar->setValue(value);
}
};
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};
//伪代码,未严格遵循cpp编码标准
//FileSplitter2.cpp
class IProgress{
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress(){}
};
class FileSplitter
{
string m_filePath;
int m_fileNumber;
List<IProgress*> m_iprogressList; // 抽象通知机制,支持多个观察者
public:
FileSplitter(const string& filePath, int fileNumber) :
m_filePath(filePath),
m_fileNumber(fileNumber){
}
void split(){
//1.读取大文件
//2.分批次向小文件中写入
for (int i = 0; i < m_fileNumber; i++){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1) / progressValue;
onProgress(progressValue);//发送通知
}
}
void addIProgress(IProgress* iprogress){
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
protected:
virtual void onProgress(float value){
List<IProgress*>::iterator itor=m_iprogressList.begin();
while (itor != m_iprogressList.end() )
(*itor)->DoProgress(value); //更新进度条
itor++;
}
}
};
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
——《设计模式:可复用面向对象软件的基础》
▷使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
▷目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
▷观察者自己决定是否需要订阅通知,目标对象对此一无所知。
▷Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
我们接到一个来自气象局的需求:气象局需要我们构建一套系统,这系统有两个布告板,分别用于显示当前的实时天气和未来几天的天气预报。当气象局发布新的天气数据(WeatherData)后,两个布告板上显示的天气数据必须实时更新。气象局同时要求我们保证程序拥有足够的可扩展性,因为后期随时可能要新增新的布告板。
这套系统中主要包括三个部分:气象站(获取天气数据的物理设备)、WeatherData(追踪来自气象站的数据,并更新布告板)、布告板(用于展示天气数据)
WeatherData知道如何跟气象站联系,以获得天气数据。当天气数据有更新时,WeatherData会更新两个布告板用于展示新的天气数据。
//subject.h
#ifndef SUBJECT_H
#define SUBJECT_H
#pragma once
//主题接口
//声明观察者类
class Observer;
class Subject
{
public:
Subject(void);
virtual void registerObserver(Observer* o) = 0;
virtual void removeObserver(Observer* o) = 0;
//当主题状态改变时,这个方法会被调用,以通知所有的观察者
virtual void notifyObserver() = 0;
~Subject(void);
};
#endif // SUBJECT_H
//subject.cpp
#include "subject.h"
Subject::Subject(void)
{
}
Subject::~Subject(void)
{
}
//observer.h
#ifndef OBSERVER_H
#define OBSERVER_H
#pragma once
//观察者接口,此例中用于更改数据
class Observer
{
public:
Observer(void);
virtual void update(float temp, float humidity, float pressure) = 0;
~Observer(void);
};
#endif // OBSERVER_H
//observer.cpp
#include "observer.h"
Observer::Observer(void)
{
}
Observer::~Observer(void)
{
}
//displayelement.h
#ifndef DISPLAYELEMENT_H
#define DISPLAYELEMENT_H
#pragma once
//显示接口,在C++中使用多重继承完成此类目的没什么必要,但为了和原著相符,这里还是定义了该接口
class DisplayElement
{
public:
DisplayElement(void);
//当布告板需要显示时,调用此方法
virtual void display() = 0;
~DisplayElement(void);
};
#endif // DISPLAYELEMENT_H
//displayelement.cpp
#include "displayelement.h"
DisplayElement::DisplayElement(void)
{
}
DisplayElement::~DisplayElement(void)
{
}
//weatherdata.h
#ifndef WEATHERDATA_H
#define WEATHERDATA_H
#pragma once
#include "subject.h"
#include "observer.h"
#include
#include
using namespace std;
//实现主题接口
class WeatherData : public Subject
{
private:
vector<Observer *> observers; //用vector容器记录观察者,在构造函数中初始化
float temperature; //温度
float humidity; //湿度
float pressure; //气压
public:
WeatherData(void);
void registerObserver(Observer *o); //注册观察者
void removeObserver(Observer *o); //取消观察者
void notifyObserver(); //通知观察者
void measurementsChanged(); //当从气象站取得新的观测值时,我们通知观察者
void setMeasurements(float t, float h, float p); //测试方法
~WeatherData(void);
};
#endif // WEATHERDATA_H
//weatherdata.cpp
#include "weatherdata.h"
WeatherData::WeatherData(void)
{
observers = vector<Observer *>();
}
WeatherData::~WeatherData(void)
{
}
void WeatherData::registerObserver(Observer *o)
{
observers.push_back(o);
}
void WeatherData::removeObserver(Observer *o)
{
vector<Observer *>::iterator iter = find(observers.begin(), observers.end(), o);
if (iter != observers.end())
observers.erase(iter);
}
void WeatherData::notifyObserver()
{
for (int i=0; i<observers.size(); i++)
{
observers[i]->update(temperature, humidity, pressure);
}
}
void WeatherData::measurementsChanged()
{
notifyObserver();
}
void WeatherData::setMeasurements(float t, float h, float p)
{
temperature = t;
humidity = h;
pressure = p;
measurementsChanged();
}
//currentconditionsdisplay.h
#ifndef CURRENTCONDITIONDISPLAY_H
#define CURRENTCONDITIONDISPLAY_H
//建立布告板
#pragma once
#include "observer.h"
#include "subject.h"
#include "displayelement.h"
#include
using namespace std;
//具体观察者,和书上一样,在此只实现其中一个布告板
//此布告板实现了Observer接口,所以可以从WetherData对象中获得改变。
//它也实现了DisplayElement接口,因此我们规定所有的布告板都必须实现此接口
class CurrentConditionDisplay : public Observer, public DisplayElement
{
private:
float temperature;
float humidity;
Subject* weatherData;
public:
//构造器需要Weather对象(也就是主题)作为注册之用
CurrentConditionDisplay(Subject* w);
void update(float temp, float humidity, float pressure);
void display();
~CurrentConditionDisplay(void);
};
#endif // CURRENTCONDITIONDISPLAY_H
//currentconditiondisplay.cpp
#include "currentconditiondisplay.h"
CurrentConditionDisplay::CurrentConditionDisplay(Subject *w)
{
weatherData = w;
weatherData ->registerObserver(this);
}
void CurrentConditionDisplay::update(float temp, float humidity, float pressure)
{
this->temperature = temp;
this->humidity = humidity; //当upadate()被调用时, 我们把温度和湿度保存起来,然后调用display()。
display();
}
//display()方法就只是把最近的温度和湿度显示出来
void CurrentConditionDisplay::display()
{
cout << "Current conditions: " << temperature
<< "F degrees and " << humidity << "% humidity" << endl;
}
CurrentConditionDisplay::~CurrentConditionDisplay(void)
{
}
//观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有者都会收到通知并自动更新
//OO原则:为交互对象之间的松耦合设计而努力
//main.cpp
#include "weatherdata.h"
#include "currentconditiondisplay.h"
//测试
int main()
{
//启动气象站
//建立主题对象
WeatherData weatherData;
//建立观察者对象
CurrentConditionDisplay currentDisplay(&weatherData);
weatherData.setMeasurements(80, 65, 30.4);
weatherData.setMeasurements(82, 70, 29.2);
weatherData.setMeasurements(78, 90, 29.2);
return 0;
}