观察者模式
“组件协作”模式:
现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
典型模式
•Template Method
•Strategy
•Observer/ Event
动机:
- 在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
- 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
模式定义:
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
——《设计模式》GoF
show me the code:
// 伪代码 实现
// 例子:文件分割器
// 把一个大文件分割成小文件,现在需要加上一个可以显示进度条的功能
class FileSplitter
{
string m_filePath;
int m_fileNumber;
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);
}
}
};
//======== UI页面===========
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();
}
};
考虑如果需求变更
进度使用label 展示,或者这是一个控制台的程序,在控制台打点显示进度,上面的代码就有问题了
首先,这种设计违背了依赖倒置设计原则
•高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。
•抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于抽象(稳定)。
progressBar 是一个细节实现,是一个控件,要想实现进度的展示,progressBar这里抽象成一个进度的通知,
调整代码,抽象出一个通知,紧耦合变为松耦合。多个进度显示场景(控制台打点,进度条显示,文本显示)可以使用通知机制,一个对象的状态发生改变,该对象依赖的所有对象都能获取到这个改变,即通知所有依赖的对象告知变化,可以把依赖的对象作为观察者维护起来,放在一个列表里面,一旦对象本身发生变化,就能通知到所有观察者。
// 抽象的通知接口
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++;
}
}
};
//=============UI================
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 << ".";
}
};
结构:
要点总结:
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。
- 目标发送通知时,无需指定观察者,通知(可以携带通知消息作为参数)会自动传播。
- 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
- Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。