观察者模式
观察者模式仍然是一个组件协作式的设计模式, 目的在于解决框架的协作问题, 我们需要为某些对象建立
一种“通知依赖关系”, 也就是说, 当一个对象(目标对象)的状态发生改变的时候, 所有的依赖对象(观察者对象)
都将得到通知。 如果这也的依赖关系过于紧密, 则过度耦合
代码
假设现在我们需要一个文件分割器, 我们需要一个界面和一个提供实际功能的类
原始状态
MainForm.cpp
class MainForm : public Form
{
TextBox* txtFilePath_;
TextBot* txtFileNumber_;
public:
void BtnClick()
{
string filePath = txtFilePath_->getText();
int number = atoi(txtFileNumber_->getText().c_str());
FileSplitter splitter(filePath, number);
splitter.Split();
}
};
FileSplitter.cpp
class FileSplitter
{
string filePath_;
int fileNumber_;
public:
FileSplitter(const string& filePath, int fileNumber) :
filePath_(filePath), fileNumber_(fileNumber) {}
void split()
{
// I
// O
for (size_t i = 0; i < fileNumber_; i ++ )
{
// code...
}
}
};
现在有个需求:假设文件巨无敌大, 那么分割会很慢, 现在需要一个进度条进行展示, 不然用户看不到进度会很焦虑
也就是说, MainForm要添加一个类似于ProgressBar的类作为成员变量, 用来展示分割进度
MainForm.cpp
class MainForm : public Form
{
TextBox* txtFilePath_;
TextBot* txtFileNumber_;
ProgressBar* progressBar_;
public:
void BtnClick()
{
string filePath = txtFilePath_->getText();
int number = atoi(txtFileNumber_->getText().c_str());
FileSplitter splitter(filePath, number, progressBar_);
splitter.Split();
}
};
FileSplitter.cpp
class FileSplitter
{
string filePath_;
int fileNumber_;
ProgressBar* progressBar_;
public:
FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
filePath_(filePath), fileNumber_(fileNumber), progressBar_(progressBar) {}
void split()
{
// I
// O
for (size_t i = 0; i < fileNumber_; i ++ )
{
// code...
if (progressBar_)
{
progressBar_->setValue(i * 100 / fileNumber_);
}
}
}
};
优化
上面写法很明显违背了依赖倒置原则, 高层模块不应该依赖底层, 二者都应该依赖抽象, 抽象不应该依赖细节,细节应该依赖抽象
什么是依赖?
A依赖B, 意思是A编译的时候, B需要存在, 这个时候才能编译通过
FileSplitter依赖ProgressBar, 那么FileSplitter编译的时候,ProgressBar需要存在, 才能编译通过
但是, 进度条的实现只有ProgressBar这么一种吗?
显然不是
为什么不应该依赖细节?因为细节非常容易变化
怎么重构呢?
依赖倒置原则给我们提供了方案, 我们应该依赖抽象, 假如ProgressBar有一个父类:ControlBase, 那么FileSplitter就依赖ControlBase
但如果是这样的话,
FileSplitter类里的for循环怎么处理?
父类一定会有这个抽象函数吗?
所以, 单纯的找父类是一个很粗浅的认识, 要明白, 咱们的ProgressBar在系统中的角色
显然:ProgressBar是一个通知, 而不是控件, 通知才是这个组件的根本属性
而控件太detail了
Interface: IProgressBar
IProgressBar.cpp
class IProgress
{
public:
virtual void DoProgress(float value) = 0;
virtual ~IProgress(){}
};
ProgressBar是一个具体的通知控件, 而IProgressBar是一个抽象的通知机制
此时就可以把FileSplitter的ProgressBar更换成IprogressBar了
然后去调用DoProgress函数
至于MainForm类的话, 使用多继承
继承Form和IProgressBar, 并且实现IProgressBar的DoProgress函数
MainForm.cpp
virtual void MainForm::DoProgress(float value)
{
progressBar_->setValue(value);
}
重构优化到这里, 已经非常松耦合了, 并且遵从依赖倒置原则
但是···如果要支持多个通知呢?也就是说, 要支持多个观察者, 怎么做?
ConsoleNotifiler.cpp
class ConsoleNotifier : public IProgress
{
public:
virtual void MainForm::DoProgress(float value)
{
progressBar_->setValue(value);
cout << ".";
}
};
第一处更改的地方, 就是将IprogressBar换成std::vector<IProgress*>, 当然, 也可以更换成List
第二处修改时增加了一个增加/删除/遍历成员的接口
ConsoleNotifiler.cpp
void addItem(IProgress* item)
{
iprogressVec.push_back(item);
}
void removeItem(IProgress* item)
{
iprogressVec.remove(item);
}
// 遍历
void forEach()
{
// code...
}
// 返回迭代器
void Begin()
{
// code...
}
void End()
{
// code...
}
然后只需要遍历每个Iprogress, 调用DoProgress函数即可
总结
观察者模式是一种组件间协作的设置模式, 用于一对多的通知机制
目标发送通知的时候, 无需指定观察者,通知(可以携带通知信息作为参数)会自动传播(调用DoProgress函数)
观察者自己确定是否需要订阅通知(订阅通知就是上面的add),目标对象对此一无所知
观察者模式常常用于基于事件的UI框架中, 也是MVC模式的一个重要组成成分
特别的, 调用DoProgress函数并不是通知观察者, 而是执行通知机制, 只是说执行结果为:通知所有的观察者