最后我们针对这4种比较可行的模拟事件方法进行一下评价,评价的标准主要考虑以下几个方面
1、 是否可以只接收事件触发类的一部分事件
2、 一个事件触发类是否支持多个事件接收类对象接收事件
3、 事件处理函数是否可以使用保护或私有的访问权限,保证事件接收类的封装特性
4、 书写方便程度与代码优雅度、简洁程度,对于这个的标准主要是看开发人员开发事件触发类(服务类)和事件接收类的代码简洁程度,不看委托类的,因为委托类一次开发完成是多次复用的,就如同C#的delegate关键字实际上是编译器使用delegate类展开实现的,我想没有几个人会去把它展开后再说是不是代码太复杂了
5、 类型转换安全程度
6、 是否支持多个对象同时接收一个事件触发类的同一个事件
7、 事件处理函数的格式是否可以任意类型
根据以上7点评价标准,可以列出如下的评价列表
方法 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
静态函数模拟事件 | 可以 | 差 | 可以 | 一般 | 一般 | 不 | 可以 |
接口模拟事件 | 不可以 | 非常差 | 不可以 | 好 | 非常好 | 不 | 可以 |
静态函数与类模板结合模拟事件 | 可以 | 好 | 可以 | 好 | 一般 | 不 | 可以 |
事件链模拟事件 | 可以 | 好 | 可以 | 好 | 一般 | 支持 | 不可以 |
注:5类型转安全程度中,对于接口方式不存在类型转换,所以非常好,对于静态函数及委托模板类的方式针对要传递的参数有类型转换,但是和这个传递参数有关的内容都是一个类来访问,所以安全性一般,如果不使用模板方式处理事件函数指针,事件函数指针也要转换类型,因为要有多个类来访问转换事件函数指针,所以安全性就非常差。
通过以上的评价比较,总结一下各种方法的使用场合
1、 静态函数模拟事件方法通常是在小的项目中,或者测试的程序中使用,灵活容易改变
2、 接口模拟事件方法通常情况下是事件触发类和事件接收类都是一个开发人员或者一个交流很好的团队开发情况下,并且一个接口所有的事件都需要接收事件类实现的时候使用
3、 静态函数与类模板结合模拟事件方法通常是对于比较大的项目,多人开发的情况下,规范整个委托事件体系的情况下来使用,其灵活性和易用性都很高,稳定性也好
4、 事件链模拟事件方法是有特殊事件链需求的时候使用,灵活性和易用性也都很高,主要的限制就在于事件不允许带有返回值,因为事件链的关系,其应用范围还是很广,大型项目也非常适用
总之,正式的项目应该是以结合使用后两种模式为主,根据情况辅助选择前两种模式,应该说第三种方法完全可以替代第一种方法,第一种对于C++程序员唯一的好处就是比较熟悉,容易理解,但是真正的效果还是跟后两种有差距。
根据以上的评价及总结,读者也可以根据需要自行选择某种方法来实现。
最后说明一点,本文的内容及例子都没有考虑多线程的情况,读者如果有兴趣可以根据需要自己设计支持多线程的版本。
在完成这个系列的文章以后,又看了网上的一些相关文章,主要有两方面可以继续变化
1、把所有事件函数的参数使用class或struct进行封装,这个类或结构体作为模板类的新增加的模板参数,这样事件触发类可以不用获取具体的模板类的事件函数指针或对象触发事件,而是通过模板类的统一方法Invoke直接触发事件,但是这个做法需要事件触发类为每个事件额外定义事件函数的参数的class或struct类型,当事件增多,参数列表变的庞大,这种方式也会为编写事件触发类的开发人员增加不少的工作量,代码简洁程度降低不少,当然事件数量少的时候,这种方式看起来更优雅一些,毕竟不存在了类似如下的代码
m_pNoParam_NoReturn_EventHandler.GetEventAndParam(&func1, &pParameter);
if (func1 != NULL)
func1(pParameter);
看起来更友好了,即系列文章中的方法声明定义少,调用复杂一些,封装事件参数法,定义变得复杂,调用简单,所以这个修改根据具体情况见人见智了。
2、使用接口与模板类结合的方法,这个方法仍然可以实现多对象接收不同事件,也可以实现多对象接收同一事件,这种做法就是要触发事件类为每一个事件函数定义一个接口,模板类的模板函数参数使用接口的函数作为参数,触发事件类调用的时候直接获得接口,然后调用,事件接收类需要选择多重继承这些接口并实现,然后就会被事件通知到,这种做法相对于系列文章中有两个好处,安全性非常好,没有数据转换,而且事件接收类不需要定义静态成员方法来接收事件,只需要实例成员方法,也就不存在了类型转换,但是这种方法对于事件触发类增加了比较多的声明代码(每个事件都需要定义一个接口),事件接收类需要继承很多接口,正是主要因为这点不太建议使用,继承太多,继承列表太长,代码不好看,另外多态方式下的性能也有影响,系列文章中分析过对于事件接收类对象的类型转换相对还是可以接受的,就像CreateThread中的那个参数一样,问题不是非常大,感觉为了这个类型转换安全度的提高,需要定义那么多接口,并继承使用,不是非常值得。