注意:该观察者模式与模板模式都很常用。百分之八十的面向对象的开发代码结构都会用到观察者模式
01 动机(Motivation)
在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系” ——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
02 模式定义
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
——《设计模式》GoF
03 样例
这是一个以前老版的文件分割器,就是以前盘小,需要将大文件分割成小的带走。现在的需求是,在UI面加上切割进度条的需求。FileSplitter.cpp指程序库代码。MainForm.cpp指用户界面。
伪代码 07
FileSplitter1.cpp
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);
}
}
};
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();
}
};
如上两个代码,违背了 依赖倒置原则,高层依赖于了低层。
如我们需要在文件传输的过程中需要显示进度条,
一般刚刚毕业的程序员可能就会如上那么做,在mainform文件和FileSplitter.cpp文件中分别设置变量
ProgressBar* progressBar和ProgressBar* m_progressBar,然后把变量m_progressBar放入到for循环之中。
那么当我们不用这种进度条的方式显示进度时,需求更改时,我们的高层程序FileSplitter.cpp就会被编译时更改。
这个代码这样做,会带给我们一个实现细节的困扰。实现细节是不断变更的,这样依赖不好。
伪代码 08
FileSplitter2.cpp
很明显在下面的代码中不管MainForm.cpp怎么改变,其实对下面的代码都是没有影响的,解决了紧耦合和依赖倒置的问题
class IProgress{
public:
virtual void DoProgress(float value)=0;
virtual ~IProgress(){}
};
class FileSplitter
{
string m_filePath;
int m_fileNumber;
//ProgressBar* m_progressBar;//这是一个具体的通知控件
//IProgress* IProgress;//这是一个抽象通知机制,支持单一观察者。
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++;
}
}
};
MainForm1.cpp
//观察者1
class MainForm : public Form, public IProgress
//C++编程中 不建议使用多继承,会造成紧耦合之类的问题,但是有种情况就是只有一个主继承,其他的都是9
{
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);
}
};
//观察者2
class ConsoleNotifier : public IProgress {
public:
virtual void DoProgress(float value){
cout << ".";
}
};
如今这个代码,进度条通知,我们可以进行随需求的更改,放置在不同的平台,都与高层代码FileSplitter2.cpp无关。
在UI界面对高层的抽象类进行具体实现,若要通知时,只需要高层虚函数来调用MainForm.cpp下层具体实现的文件
。
04 结构
红色圈画的是稳定的,蓝色圈画的是变化的。
05 要点总结
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达致松耦合。
目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
观察者自己决定是否需要订阅通知,目标对象对此一无所知。//说的普通点,就是通知进度的代码,不在分割的代码里面包含了。
Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。