1. 意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于 它的对象都得到通知并被自动更新 。
2. 动机
在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
3. 示例
假设我们需要实现一个文件分割器,在split函数里面实现大文件的分割。
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++) {
//...
}
}
};
我们新建一个窗口,定义在一个按钮被点击时开始使用文件分割器类分割文件。
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();
}
};
如果我们想在窗口中添加一个进度条,那么我们需要在窗口类和文件分割器类都中添加一个类型为ProgressBar*的进度条成员,并向文件分割器类的构造函数添加构造这个进度条的方法,修改split函数使其支持显示进度条。
这么做违背了依赖倒置原则,ProgressBar进度条属于实现细节,后期如需更换进度条为其它组件需要再次修改这些代码。我们可以使用一种抽象的方式表达ProgressBar进度条这样一种通知,通过定义一个接口来决定通知的形式。
class IProgress{
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress() {}
};
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;
m_iprogress->DoProgress(progressValue); // 更新进度条
}
}
};
然后我们再让窗口类继承这个接口,重写DoProgress函数,并将this指针传递给分割器的构造函数。
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());
FileSplitter splitter(filePath, number, this);
splitter.split();
}
virtual void DoProgress(float value) {
progressBar->setValue(value);
}
};
我们可以继续修改分割器类,使其可以通知多个组件。首先,我们创建元素类型为IProgress*的m_iprogressList列表。其次,我们添加加入和删除通知机制的函数。最后,将通知行为代码抽象为onProgress函数,每次都通知列表中的所有元素。
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.add(iprogress);
}
void removeIProgress(IProgress* iprogress){
m_iprogressList.remove(iprogress);
}
protected:
virtual void onProgress(float value){
for (auto item : m_iprogressList) {
item->DoProgress(value);
}
}
};
这样我们的分割器就可以支持更新多个组件。窗口类也需做对应的修改。
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 << ".";
}
};
我们也可以将addIProgress、removeIProgress和onProgress函数以及m_iprogressList成员放到基类中,这里就不给出代码示例了。
4. 结构
5. 定义
定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
专属学习链接:https://xxetb.xetslk.com/s/32bi94
6. 要点总结
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标(被观察者)与观察者,从而使二者之间的依赖关系达致松耦合。
目标(被观察者)发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播。
观察者自己决定是否需要订阅通知,目标对象对此一无所知。
Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
标签:Observer,filePath,int,void,观察者,IProgress,fileNumber,public From: https://blog.csdn.net/dswwedxc/article/details/140993773