以下内容均来自GeekBand极客班C++ 设计模式课程(李建忠老师主讲)
完整代码见:https://download.csdn.net/download/qq_41605114/18881788
Observer
“组件协作”模式:
现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作模式”通过晚绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
典型模式:
Template Method
Strategy
Observer/Event
动机(Motivation)
在软件构建过程中,我们需要为某些对象建立一种“通过依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
GOF定义:
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
蓝色为稳定部分,橘黄色为变化部分
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
一种通知机制。
下面我们以文件分割器为示例
结构化编程示例
下面是文件分割器:
class FileSplitter{
string m_filePath;
int m_fileNumber;
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){
//...
}
}
};
下面是MainFrom主界面
class MainForm : public Form{
TextBox * txtFilePath;
TextBox * txtFileNumber;
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath,number);//创建分割文件对象
splitter.split();
}
};
现在要在文件分割的过程中提供一个进度条的功能
最简单和直观的做法如下:
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){
//...
if(m_progressBar){
m_progressBar->setValue( (i + 1)/m_fileNumber );//更新进度条
}
}
}
};
class MainForm : public Form{
TextBox * txtFilePath;
TextBox * txtFileNumber;
ProgressBar * m_progressBar;//更新进度条
public:
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath,number,m_progressBar);//创建分割文件对象
splitter.split();
}
};
现在如果要求更改ProgressBar的表达形式,比如打点显示,直接百分比,那么以上代码修改的地方就不是一点两点
ProgressBar是一种“通知”的具体表现形式,FileSplitter类目前对于ProgressBar是一种紧耦合,如果现在需要更新通知机制,那么显然不符合依赖倒置原则。
观察者模式
现在增加一个抽象类:
class IProgress{
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress(){}
};
class FileSplitter
class FileSplitter{
string m_filePath;
int m_fileNumber;
// ProgressBar * m_progressBar;//具体通知控件
IProgress * m_iprogress;//抽象通知机制
public:
FileSplitter(const string & filePath,int fileNumber,IProgress * iprogress){
m_filePath(filePath);
m_fileNumber(fileNumber);
m_iprogress(iprogress);
}
void split(){
//1,读取大文件
//2,分批次向小文件中写入
for(int i = 0;i < m_fileNumber;++i){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1)/progressValue;
onProgress(progressValue);//抽象通知机制
}
}
protected:
//重构,考虑子类是否会重写这个部分的功能
virtual void onProgress(float value){
if(m_iprogress){
m_iprogress->DoProgress( value );//抽象通知机制
}
}
};
FileSplitter类目前是松耦合的状态,has 一个抽象类,这个抽象类中进度通知部分,可以交给具体的情况去完成。
下面是界面类:
class MainForm : public Form , public IProgress{
//多继承,一个父类,剩下的都是接口,是可以的
TextBox * txtFilePath;
TextBox * txtFileNumber;
ProgressBar * m_progressBar;
public:
virtual void DoProgress(float value){
m_progressBar->setValue(value);
}
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
FileSplitter splitter(filePath,number,m_progressBar);//创建分割文件对象
splitter.split();
}
};
进度条具体的实现,直接放置MainForm中进行实现。
那么上面的代码是单一通知机制,如果要有多个通知呢?上面的例子中,只有MainForm一个观察者,要完成上面的要求也很简单,直接使用一个容器即可。
class IProgress{
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress(){}
};
class FileSplitter{
private:
string m_filePath;
int m_fileNumber;
// ProgressBar * m_progressBar;//具体通知控件
IProgress * m_iprogress;//抽象通知机制 支持一个观察者
list<IProgress *> m_iprogressList;//抽象通知机制 支持多个观察者
public:
//单个观察者的构造函数
FileSplitter(const string & filePath,int fileNumber,IProgress * iprogress){
m_filePath(filePath);
m_fileNumber(fileNumber);
m_iprogress(iprogress);
}
//多个观察者时,需要增加接口函数
FileSplitter(const string & filePath,int fileNumber){
m_filePath(filePath);
m_fileNumber(fileNumber);
}
void addIprogress(IProgress * iprogress){
m_iprogressList.push_back(iprogress);
}
void removeIprogress(IProgress * iprogress){
m_iprogressList.remove(iprogress);
}
void split(){
//1,读取大文件
//2,分批次向小文件中写入
for(int i = 0;i < m_fileNumber;++i){
//...
float progressValue = m_fileNumber;
progressValue = (i + 1)/progressValue;
onProgress(progressValue);//抽象通知机制
}
}
protected:
//重构,考虑子类是否会重写这个部分的功能
virtual void onProgress(float value){
//单个观察者
if(m_iprogress){
m_iprogress->DoProgress( value );//抽象通知机制
}
//多个观察者
for(auto item : m_iprogressList){
item->DoProgress( value );
}
//迭代器写法
list<IProgress *>::iterator itor = m_iprogressList.begin();
while(itor != m_iprogressList.end()){
(*itor)->DoProgress( value );
itor++;
}
}
};
class MainForm : public Form , public IProgress{
//多继承,一个父类,剩下的都是接口,是可以的
TextBox * txtFilePath;
TextBox * txtFileNumber;
ProgressBar * m_progressBar;
public:
virtual void DoProgress(float value){
m_progressBar->setValue(value);
}
void Button1_Click(){
string filePath = txtFilePath->getText();
int number = atoi(txtFileNumber->getText().c_str());
//单个观察者
FileSplitter splitter(filePath,number,this);//创建分割文件对象
//多个观察者
splitter.addIprogress(this);
splitter.addIprogress(&other);
splitter.split();
}
};
//假设下面是第二个观察者
class ConsoleNotifier : public IProgress{
};
要点总结
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系转化为松耦合。
目标发送通知时,无需指定观察者,通知会自动传播
观察者自己决定是否需要订阅通知,目标对象对此一无所知
Observer模式是基于时间的UI矿建中非常常用的设计模式,也是MVC模式的一个重要组成部分