Observer 观察者模式
意图
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。
Observer(观察者模式),或者又叫Event(事件模式),是我们在编写UI程序时一定会使用到的一种设计理念。Java中的EventListener接口就是完成的这一功能。
- 当一个类模块需要从另一个类模块获取信息,而这两者又无法用继承、组合的方式来描述时。
- 获取通知的的模块需要获取发送通知的模块的信息,而发送通知的模块事实上不需要知道前者的存在时。
在上述情况满足的情况下,为了降低对象之间的耦合度,我们引入Observer来实现我们的目标。
从稳定-变化的角度来分析的话,使用Observer的程序,接受者获取通知的方式是稳定的,而发送者和接收者可以随时变化。
代码案例
考虑下面的类, 它描述了企业的财务报表情况。
class Financial
{
private:
//若干信息,我们这里抽象成一个int类型。
int info;
public:
//接口
int getInfo();
void changeInfo();
}
如上,为了方便,我们把若干财务信息简写成一个int,然后把它置为Private,这显然是处于封装性良好的目的。如果想要获取信息,则需要调用公有函数getInfo()。
现在,我们的需求是,让info以图形化的显示方式显示出来,比如饼状图,柱状图之类的,并且随info的更新而实时更新。这是一个很常见的功能。
想法一:把一个GUI组件组合到该类中,Info改变时顺势改变GUI。
//IDEA 1
class Financial
{
private:
Pie pie;
int info;
public:
//接口,
int getInfo();
void changeInfo();
}
这显然是有问题的。
- 逻辑上说不通:财务报表 Has-A Pie?
- 硬编码,难修改,难扩展。如果我不想用饼状图呢?换成柱状图的过程代价或许很大。或者说我可能需要多种GUI同时显示?扩展性差。
- 违反了依赖倒置原则。这才是最严重的问题:我在编写一个反映公司财务信息的类之前,必须要编写一个Pie类,否则无法通过编译。以我们的常识来看,显然,一个GUI组件的层级应当高于Financial类,然而,为了编写一个低层次组件,我必须先实现一个高层次组件,就好像先建1楼再打地基,这是行不通的。
现在,我们尝试着用Observer的思想来解决这个问题。
class BaseObserver
{
virtual void Update() = 0;
}
class Financial
{
private:
BaseObserver* observer;
int info;
public:
//接口
int getInfo();
void attach(BaseObserver *b){observer = b;};
void detach(){delete observer;};
void changeInfo();
}
我们定义了一个Observer基类。并且,在原来的Financial类中,我们组合一个Observer组件(事实上是多态指针)。现在,我们来编写changeInfo()函数。
void Financial::changeInfo()
{
//无关代码
...
if(observer != NULL)
observer -> Update();
//无关代码
...
}
这里,在changeInfo的函数体中,observer得到了通知并成功更新。
那么,如果我们想使用一个饼状图,只需要:
class Pie : public BaseObserver
{
...
void update(){...}
...
}
这里的BaseObserver就相当于Java中的EventListener接口。不过C++语法中没有接口这一功能罢了。采用继承虚基类可以模拟继承接口,效果是一样的。
当然别忘了,我们还需要向通知发送者注册才能获取信息:
Financial f;
Pie* pie = new Pie();
f.attach(pie);
这里只实现了一个发送者到一个接收者。事实上,将BaseObserver*改为 vector<BaseObserver*>, 也可以轻松实现多个接收者。只需要原本的observer -> Update()改为循环遍历即可。
解释
在Observer模式中,我们实现了发送者和接收者的松耦合关系,使得发送者完全不需要依赖于实际的接收者,而只依赖于它们的基类,这满足了依赖倒置原则。
Observer模式试图维护多个模块的一致性(在上面的例子中,也就是UI界面和数据的一致性),同时避免将两个不相干的模块紧耦合。
上面的做法只是一种实现方式。还有一种实现方式是:将Event事件作为一种对象,抽象出一种Event基类,用继承自Event的子类作为第三方转发事件信息。这种做法与上述思想相同,但做法略有不同,因此后者常称为Event模式。Qt的信号-槽机制就是后者的应用。
你会在GUI的编写中常常用到observer模式,但通常只是使用:你大概会通常采用使用Observer模式的第三方图形库或平台,而不会亲自编写它。但它在程序的其他组件上也大有用武之地
总结
设计模式 | Observer(观察者模式) |
---|---|
稳定点: | 通知的发送-接收流程 |
变化点: | 发送者和接收者本身 |
效果: | 维护了不相干模块间的一致性,同时避免紧耦合 |
特点: | 发送者依赖于观察者基类 |
2021.1.27 转载请标明出处