观察者(Observer)模式
应用场景
某一天,你的老板希望你在某个项目的文件下载的界面,加一个进度条,方便用户知道现在文件下载到那一步了。于是乎,你写了个文件加载类(FileLoader),一个进度条类(ShowRate)。把文件加载的进度实时地显示给用户。就像这样
class ShowRateA{
public:
void setRate(float rate){
mRate = rate;
}
void showRate(){
//showRate
}
private:
float mRate;
}
class FileLoaderA{
public:
FileLoader(ShowRate* showRate)
:mShowRate(showRate){}
void loadFile(const char *fliePath){
//假设有总共10个文件
for(int i = 0;i<10;i++){
//加载文件
mShowRate->setRate(i/10.0);
mShowRate->showRate();
}
}
private:
ShowRate* mShowRate;
}
int main(void){
ShowRateA* showRate = new ShowRateA();
FileLoaderA *fileLoader = new FileLoaderA(showRate);
fileLoader->loadFile("一个神奇的文件路径");
delete fileLoader
delete showRate;
return 0;
}
看起来好像是解决了问题。实际上这时候你确实解决了问题。
but过了几天,你的老板发现,之前的进度展示界面好像做的太难看了,于是找人写了个更好看的界面,于是需要一个新的进度条(ShowRateB),你需要的是把之前的进度条换掉,这时候你只能重新编译main函数,并且重新编译一大堆文件,导致软件出现一大堆莫名其妙的bug,于是你加班到12点,第二天疯狂掉头发,有过了两天,老板发现之前的文件加载类不够高效,于是找人写了个新的文件加载类(FileLoaderB),于是又要重写main函数,然后一大堆bug又来了,又加班,掉发。
最后秃头。
为什么你会秃头呢?因为这样写代码严重违反了依赖倒置原则,极有可能造成莫名其妙的代码错误。(之前有一篇博客专门讲了软件设计6大原则)
使用观察者模式的目的
定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象,在主题对象的状态发生变化时,会通知所有的观察者。
隔离变化
在实际的项目中,使用配置文件+合理的设计模式,可以做到只更新需要的一部分,
class Observer{
public:
virtual void update(float rate) = 0;
virtual void showRate() = 0;
virtual ~Observer();
};
class ShowRateA : public Observer{
public:
virtual void update(float rate){
mRate = rate;
}
virtual void showRate(){
//showRate
}
private:
float mRate;
};
class Subject{
public:
virtual void loadFile(const char *fliePath) = 0;
virtual ~Subject();
};
class FileLoaderA : public Subject{
public:
FileLoaderA (Observer *observer)
:mObserver(observer){}
virtual void loadFile(const char *fliePath){
//假设有总共10个文件
for(int i = 0;i<10;i++){
//加载文件
mObserver->update(i/10.0);
mObserver->showRate();
}
}
private:
Observer *mObserver;
};
int main(void){
Observer *showRate = new ShowRateA();
Subject *fileLoader = new FileLoaderA(showRate);
fileLoader->loadFile("一个神奇的文件路径");
delete fileLoader;
delete showRate;
return 0;
}
这样写的话就算你再写了个新的ShowRateB或者新的FileLoaderB,也能很好的支持,不需要改写各自代码,可以有人会想,这样写也不main函数,实际上一般情况下,决定加载哪一个类的不是main函数里面的那几行代码,实际上很多软件系统都有xml文件,软件通过加载xml文件来决定加载哪一个类。当你需要更新一个类时,只需要编译新的那个类,然后改写xml文件就行了。(这个操作设计到后面要讲的工厂模式,这里可以先听着,后面会讲)
类图
Subject(目标)
——目标知道它的观察者。可以有任意多个观察者观察同一个目标;
——提供注册和删除观察者对象的接口。
Observer(观察者)
——为那些在目标发生改变时需获得通知的对象定义一个更新接口。
ConcreteSubject(具体目标)
——将有关状态存入各ConcreteObserver对象;
——当它的状态发生改变时,向它的各个观察者发出通知。
ConcreteObserver(具体观察者)
——维护一个指向ConcreteSubject对象的引用;
——存储有关状态,这些状态应与目标的状态保持一致;
——实现Observer的更新接口以使自身状态与目标的状态保持一致。
适用场景
在以下任一情况下都可以使用观察者模式:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立的改变和复用;
- 当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变;
- 当一个对象必须通知其它对象,而它又不能假定其它对象是谁;也就是说,你不希望这些对象是紧密耦合的。