一、模式总结
组件协作模式分类,通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。
依然是一个非常常用的模式。主要在讲,一个地方有了修改,其他地方如何接收这些修改。改进方向为符合依赖倒置原则------高层应用细节不应该影响底层设计。
书本中的定义:定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
1.原始版本
一个应用,需要将一个大文件进行拆分为多个小文件。拆分过程中,要显示进度。此示例,变化的地方在于,进度条的展现方式可能多种多样,可能需要在不同的界面展示进度。
如下代码,最开始的需求是,只需要在界面中的进度条中,将当前进度展示出来就好。
scss
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 != nullptr){
float progress = m_fileNumber;
progress = (i + 1) / progress;
m_progressBar->setValue(progress);
}
}
}
};
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.spilt();
}
};
2、变更版本
现在有了需求变更,我们不是要在进度条中展现,而是同时在一个文本控件中将具体的进度值进行展示。那么上面代码中的两个类都需要改。可以有以下改进,让需求变更只需要在界面代码中改便好。
csharp
class IProgress {
public:
virtual void DoProgress(float value) = 0;
};
class FileSplitter {
string m_filePath;
int m_fileNumber;
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 progress = m_fileNumber;
progress = (i + 1) / progress;
onProgress(progress);
}
}
protected:
void onProgress(float value){
if(m_iprogress != nullptr){
m_iprogress->DoProgress(progress);
}
}
};
class MainForm : public Form, public IProgress {
TextBox* txtFilePath;
TextBox* txtFileNumber;
ProgressBar* progressBar;
// Label* progressLabel;
public:
void Button1_Click(){
string filePath = txtFilePath.getText();
int number = atoi(txtFileNumber.getText().c_str());
// 四年后再次看到这里,依然感觉此处很帅
FileSplitter splitter(filePath, number, this);
splitter.spilt();
}
virtual void DoProgress(float progress) {
progressBar->setValue(progress);
// progressLabel->setString(str(progress)); // c++么得str()
}
};
3、更加完善的版本
上面的改进版本,已经符合依赖倒置原则。
其实还是有点问题的,对于上面的FileSplitter来说,只能通过构造函数为它添加一个Observer,当它的一个对象为全局的时候,在好几个界面中需要展示进度,则办不到了,不可能每次都为构造函数加参数。
以下改进,便可以支持多个观察者。
csharp
class IProgress {
public:
virtual void DoProgress(float value) = 0;
};
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 progress = m_fileNumber;
progress = (i + 1) / progress;
onProgress(progress);
}
}
void addIProgress(IProgress* iprogress) {
m_iprogressList.add(iprogress);
}
void removeIProgress(IProgress* iprogress) {
m_iprogressList.remove(iprogress);
}
protected:
void onProgress(float value){
List<IProgress*>::iterator iter = m_iprogressList.start();
while(iter != m_iprogressList.end()){
(*iter)->DoProgress(progress);
iter++;
}
}
};
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);
splitter.addIProgress(this);
splitter.spilt();
}
virtual void DoProgress(float progress) {
progressBar->setValue(progress);
}
};
二、项目中的使用
最开始接触这种方式是在G15中,有一个全局模块作为Dispatcher,许多个模块(主要是UI界面)作为Listener。实现方式,就是在Dispatcher身上放了许多个Listener的相同参数函数,当Dispatcher身上某个数据改变的时候,就遍历这许多Listener放这边的函数,一一调用。
当时看到这一套消息监听的方式,觉得很高级,在看完这一篇教程后,发现这就是Observer(观察者)设计模式啊。
在现在项目中,也使用了这一套流程。Dispatcher提取出来一个基类,需要被许多Listener监听消息的模块,继承Dispatcher。Python中的实现与上面示例大同小异,唯一区别是提取出来了一套基类。
三、各种tip
1、软件设计中的细节,是极其容易变化的。底层设计不要依赖高层细节。
2、Java中的接口,在C++中,其实就是一个抽象基类。
3、多继承都是不推荐使用的。但是推荐一种继承方式,继承的父类为实类,其他的都只是接口。就是上面示例中的IProgress。
4、看这一系列视频对于理解C++,还真是有许多的帮助。
5、所写代码风格,和我所遵守的风格很是接近,这一套视频看起来真的很安逸。哈哈。
6、才第3个设计模式,发现比前面两个会难一点。不过依然在项目中有所使用。(然后四年后的我有将这一课完整再听一遍,老师讲的真好。)
7、上午和中午看视频,晚上自己进行文档整理的时候,还是有一些细节需要确认。再重新听一遍,竟然还是能够听进去。而且对讲课内容的理解,有了些变化。(这份笔记,四年后的我并没有很多改动。)