这里,我们设计一个文件分割的例子,虽然现在用的不多,因为现在的存储介质容量都相对很大,但是作为一个例子来说明设计模式的解耦思维.
#include
#include
#include
using namespace std;
// 声明一个文二建分割器Class
class FileSplitter
{
public:
// 使用初始化列表初始化私有成员变量
FileSplitter(const std::string& filePath,int fileNumber):m_filePath(filePath),m_fileNumber(fileNumber){}
public:
void split()
{
// 1. 读取大文件
// 2. 分批次从向小文件中写入
for (int i = 0; i < m_fileNumber; i++)
{
// ... 文件写入工作
}
}
private:
std::string m_filePath;
int m_fileNumber;
};
// 声明一个窗体类,继承自窗体基类,类似Qt或MFC,其中Form类是伪代码假设
class Form{};
class TextBox{};
class MainForm:public Form
{
public:
void Button_Click() // 一个点击事件方法
{
std::string filePath = txtFilePath->getText();
int num = atoi(txtFileNumber->getText.c_str()); // 文本转化为整型
FileSplitter splitter(filePath,num);
splitter.split();
}
private:
TextBox* txtFilePath;
TextBox* txtFileNumber;
};
int main(){
MainForm main_window; // 然后做点击操作
rentrun 0;
}
到此,一个简单的文件分割窗口程序伪代码就写好了。如果现在用户提出一个需求:如果文件过大,文件分割可能持续较长时间,能否设置一个进度条,实时查看文件分割的进度?可能实现的解决方案:
if(m_progessBar!=nullptr)
m_progessBar->setValue((i+1)/(double)m_fileNumber*100);
如果将来某天,用户提出需求,不在使用进度条了,要使用文本框或者控制台的方式怎么办?按照当前的设计方式,我们一定在代码迭代时需要同时修改MainForm和FileSplitter中的代码,这就不是运行时依赖了,而是典型的编译时依赖,这就不是一种稳定的代码设计方式.
从代码设计原则上看,上述代码违背了OOP八大设计原则的第一条:依赖倒置原则(DIP),即:
总的来说,上述实现方式是编译时依赖而不是运行时依赖的,最完美的程序设计需要运行时依赖. 我们不能保证以后的进度通知时通过哪种具体的方式,甚至通知的数量,因此我们要在高层和底层之间增加一层抽象.
思路:其中ProgressBar体现了一种消息的通知,而不是具体的展现方式.
#include
#include
#include
using namespace std;
// 使用OOP多态,在高层和底层之间增加一层抽象
class IProgress {
public:
virtual void DoProgress(float value) = 0; // 从一个具体的控件,到一个消息通知的抽象
virtual ~IProgress(){}
};
class FileSplitter:public IProgress // 底层细节继承中间抽象层
{
public:
virtual ~FileSplitter() {}
// 使用初始化列表初始化私有成员变量
FileSplitter(const std::string& filePath,int fileNumber):m_filePath(filePath),m_fileNumber(fileNumber){}
public:
void split()
{
// 1. 读取大文件
// 2. 分批次从向小文件中写入
for (int i = 0; i < m_fileNumber; i++)
{
// ... 文件写入工作
if(m_progessBar!=nullptr)
this->DoProgress((i+1)/(double)m_fileNumber*100);
}
}
void DoProgress(float value){ /* 消息通知的代码 */} // 实现进度消息的通知
// 使用容器添加多个观察者对象
void addIProgress(IProgress* iprogress)
{
m_iprogressList.push_back(iprogress);
}
void removeIProgress(IProgress* iprogress)
{
m_iprogressList.remove(iprogress);
}
protected: // 使用容器管理多个容器
virtual void onProgress(float value)
{
for (auto iter = m_iprogressList.begin; iter != m_iprogressList.end; iter++)
iter->DoProgress(value);
}
private:
std::string m_filePath;
int m_fileNumber;
IProgress* m_iprogress; // 使用抽象
std::list<IProgress*> m_iprogressList; // 使用容器改良,可以同时通知多个观察者
};
// 界面窗体
class MainForm:public IProgress // 使用抽象后
{
public:
void Button_Click() // 一个点击事件方法
{
std::string filePath = txtFilePath->getText();
int num = atoi(txtFileNumber->getText.c_str()); // 文本转化为整型
FileSplitter splitter(filePath,num);
splitter.split();
}
public:
// Override 通过继承的方式实现虚方法
virtual void DoProgress(float value){
// 实现具体消息通知的代码
}
private:
TextBox* txtFilePath;
TextBox* txtFileNumber;
};
### 2.4 结果分析
看起来观察者模式与策略模式有点相似,但在死路上有本质的区别。策略模式强调扩展性,而观察者模式强调依赖倒置.
## 三、要点总结
1. 使用OOP的抽象,观察者模式使得我们可以独立地改变目标和观察者,从而使两者之间的依赖关系达到松耦合;
2. 目标发送通知时,无需指定观察者,通知(可以携带通知信息作为参数)会自动传播;
3. 观察者自己决定是否需要订阅通知,目标对象对此一无所知;
4. 观察者模式基于事件的UI框架中非常常用的设计模式,一个MVC模式的一个重要组成部分;
5. 非UI框架时,在回调函数中也使用非常多.