10.1 泛化观察者模式简述
常常我们的系统中会出现一个问题就是当某个条件或者数据改变时候,依赖这些数据或者条件的一些系统也需要进行相应的改变。而这个改变时间是不可预料的,所以如果让这些系统自动定时检测条件的改变虽然是可行的,但是却是很低效的。这就好比每个学院的网页管理员总是希望每当学校的某些东西变化的时候(例如:校历,考试安排等),总是希望学院的对应网页能自动响应学校的变化而不用人为的更新,如果一个学院的一个管理员更新这些消息要花1小时/天,那一个学校有30多个学院也就是说每天需要30小时的人力来更新这些琐碎的事,如果将这些时间节约下来,就能节约一个人的生命。聪明的办法是当学校有数据变化时候就发出一个广播给各个学院,当学院接到广播以后则根据自己的需要更新数据。这就是观察者模式的产生需要。
通常实现上面描述的代码是需要在程序中通过一个Subject类来维护若干观察者的一个继承体系基类ChangeManager,而所有的观察者都必须继承自ChangeManager,这也就意味着所有的观察者都必须有一定的耦合关系(详情见【GoF】)。当我们需要添加一个观察者时候就必须继承自ChangeManager,如果我们有一套算法是在很久以前实作出来,所以并没有继承自ChangeManager,那要运用到观察者模式就必须修改曾经的代码(必须继承自ChangeManager)再重新编译。
泛化的目的就是在最大程度上提高代码的复用性,而这就需要我们尽最大努力地消除各个观察者之间的耦合性,当我们需要往观察者队列中添加观察者时候不需要继承自ChangeManager,这就是我们泛化观察者模式的目的。
10.1.1 定义
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
10.1.2 泛化目的
由于传统的观察者模式虽然达到添加删除观察者不需要修改通知端代码的目的,但是却使得所有观察者都必须有亲属关系(必须继承自ChangeManager),使得一些没有继承自ChangeManager得老代码必须修改代码和重新编译才能使用,这很大的限制了观察者模式的复用性。泛化观察者模式的目的就是在保持其原先优点的条件下根本上消除观察者之间的耦合关系。
10.1.3 适用场合
在以下情况下适合使用泛化观察者模式:
l 当一个抽象模型有两个方面,其中一个方面依赖于另外一个方面。将这二者封装在独立的对象中以使他们可以独自的改变和复用。
l 当一个对象的改变需要同时改变其它对象,而不知道也不必知道具体有多少对象需要改变。
l 当一个对象必须通知其他对象,而当它有不能假定其它对象是谁。
l 当一个已有的对象在不用修改源代码的条件下需要在其他对象改变时候得到通知。
l 当需要得到改变通知的对象之间没有直接联系的时候(没有共同继承自一个基类)。
l 所有需要改变的对象都必须包含一个update函数以供改变时候通知调用。
10.2 泛化观察者不足
虽然所有的观察者可以无耦合,但是也必须拥有一个update的成员函数,这使得一些陈旧代码依然无法很好的加入到观察者当中,但是幸运的是配接器(Adapter)可以帮助我们解决这个问题。
所有观察者的update函数都必须拥有相同的参数类型和个数,并且现在没有一个比较好的方法解决update函数的非空返回值存储问题(【GoF】中update统一为空返回值)。
10.3 实作泛化观察者
10.3.1 需要解决问题的分析
泛化观察者模式相比于传统观察者模式最大的不同就是要消除观察者之间的耦合关系,其实类似这样的问题在前面的设计模式泛化中常常会遇到,例如泛化策略模式。
我们首先会想到需要一个容器存储这些观察者,这样我们就可以统一调用其update函数,但是问题出在这些观察者的类型我们是未知的,并且可以肯定的是这些观察者之间没有绝对的耦合关系,如果用list,vector等常规的容器存储这些观察者是绝对不行的,我们需要一个可以存储未知类型且可以在运行期动态添加删除的容器。
在只存在面向对象的C++世界中是没有办法解决这个问题的,因为所有的容器在定义的时候都必须给定一个数据类型以分配内存空间。但是C++不只是面向对象的语言,其中还有很强大的泛型技法,当然对于很多认为泛化只是简单的将T替换成相应数据类型的人们,这依然是无解的。泛化的强大只有和面向对象完美结合的时候才能得到惊人的效果。(如果你现在任然不理解这句话,真请你好好地再回头看看前面几章的知识。)
在第八章中我们介绍了泛化信号模式,它是一个可以动态存储一系列算法并可以统一调用的容器,并且泛化很好的使得这些算法没有直接的耦合性,这正是我们这里所需要。
看到这里我希望那些对我的作品不够重视的人们可以改变一下观点了,通过我的证明泛化设计模式并不是一纸空谈,其有很高的实用价值,也许我的代码不够完善,但是这只能成为否定我的理由而不是否定泛化设计模式的理由,更不能是否定泛化这个概念的理由。
10.3.2 实作泛化观察者模式
下面你将看到简练而不简单的代码,这代码是运用到了我先前的成果,所以很大程度上减少的代码编辑量,这也很好的验证了泛化设计模式的实用性。
如果你看过【GoF】中的观察者模式,你就应该有印象其中通过了三个类实现了观察者模式,而这里我将只用一个Publish类就实作观察者模式。
//第一个模板参数是更新函数返回值,默认为空;第二个模板参数是更新函数参数类型,默//认为TypeVector<>
template<typename ResultType = void,typename TVector = TypeVector<> >
class Publish
{
//这里创建一个泛化信号模式容器
Signal< ResultType,TVector > sig ;
public:
//我们需要一些成员函数能够维护PubList
Publish()
{
}
//往观察者队列中添加观察者
template <typename T>
void add(T& add_class)
{
sig.connect(add_class);
}
//情况观察者队列
void clear()
{
sig.clear();
}
//现在我们需要一个函数能够激活观察者对了的更新函数
void update()
{
sig();
}
};
上面程序因为调用上一章的成果使得每个函数都显得很是简练,却充满了泛化的结晶,人类进步总是在踩在前人的肩膀,而不是从头开始。
使用例子,假设我们有两个时钟对象,当时钟的计数变量i达到5的倍数时候激活时钟对象的更新函数。
//时钟变量i
int i=0;
//时钟响应类
class Clock_i
{
int i_temp;
public:
void operator()()
{
update();
}
void update()
{
i_temp = i;
print();
}
void print()
{
std::cout<<"Clock_i::i = "<<i_temp<<std::endl;
}
};
//另一个时钟响应类
class Up_i
{
int i_temp;
public:
void operator()()
{
update();
}
void update()
{
i_temp = i;
print();
}
void print()
{
std::cout<<"Up_i::i = "<<i_temp<<std::endl;
}
};
//调用方法
int main()
{
//产生一个观察者容器,注意模板采用的默认参数
Publish<> pub;
//生成第一个响应对象
Clock_i clock;
//生成第二个响应对象
Up_i up;
//将响应对象加入队列
pub.add(clock);
pub.add(up);
//检测时钟的改变,并且在时钟为5的倍数时候调用观察者队列的更新函数
while(i<=50)
{
++i;
if(i%5==0)
{
//其实这里我们并不知道会有哪些对象得到更新
pub.update();
}
}
getchar();
return 0;
}
10.4 摘要
l 泛化观察者模式能最大限度的降低观察者之间的耦合关系。
l 暂时没有很好的办法处理更新函数的返回值,不过这通常不过存在应用上的困难。
l 所有同一队列中的观察者都必须具有相同更新函数的参数个数以及类型,当然返回值类型也必须相同。