俗语中,笑话一个地方穷,就经常说“通讯基本靠吼,交通基本靠走”。无独有偶,今天我们也要介绍一个类似于通讯基本靠吼的事件处理方式——EventAggregator。
传统事件处理方式
首先回想一下,我们怎么完成事件处理?简单的三步。
- 定义主体(被收听者)的暴露的事件类型。
- 在订阅者中定义事件处理程序并关联到主体。
- 主体触发事件。
非常简单的结构,如下所示。订阅者依赖于主体。
如果一个主体有多个订阅者,那么多个订阅者将会依赖于同一个主体。
如果情况再变化一下,多个主体有多个订阅者,那么依赖就会更加复杂。
看到这里,相信大家已经明白了,这种传统的事件处理模式有如下弊端。
-
订阅者依赖于主体,这样会造成模块之间的强耦合,而解耦是优秀设计一直追求的目标。
-
订阅者必须要得到主体的引用,才能订阅主体事件。在某些情况下这个要求很难达到,有时为了达到目的,引用会在几个对象之间不停传递。
为了克服这些弊端,我们来试试事件总线——EventAggregator。
事件总线
参考设计模式中中介者模式的设计思想,我们引入一个事件总线充当中间人,无论订阅者也好,主体也好,都只和这个事件总线打交道。这个总线再以广播的形式把事件给广播出去——这也是通讯基本靠吼这个调侃的由来——同时,它带来的好处显而易见,再也不需要仅仅是为了订阅事件而辛苦的维持主体引用了。
这样的好处在于,无论有多少订阅者和主体,订阅关系的处理全权委托给了事件总线。同时双方只依赖于事件总线,不再相互依赖。而事件总线在工程里面一般位于Infrastructure这种基础公共类库里面,处于上层模块的主体和订阅者依赖于Infrastructure并无不妥。以这种依赖为代价换取之前的主体模块和订阅者模块之间的依赖,对于程序结构也是一大进步。
一个例子
俗话说,光说不练假把式。让我们看一个例子,试着更深入的了解EventAggregator的好处。
设想一个游戏编辑器,有主界面,拥有地图选择器可以选择不同的世界,也可以切换不同的操作界面风格,而具体的地图编辑器,单元格编辑器则需要根据所选世界的不同而动态装载它们所呈现的内容。当然,编辑器们也需要对操作界面风格的改变做出响应,最后,所有这些改变都需要通知日志记录器。
可以想象,这一切如果用传统的事件模型会多么复杂,地图编辑器,单元格编辑和日志记录器需要依赖地图选择器和操作界面风格选择器,而且如果之后这些子编辑器有任何新的扩展,这些新类都需要依赖于选择器。
我们试着用EventAggregator来处理。
使用EventAggregator来处理事件订阅非常简单,只需如下几步。
- 自定义或引用EventAggregator
我们引用Prism.Core类库,该类库提供了EventAggregator的实现。
加入命名空间声明。
using Prism;
using Prism.Events;
- 定义事件
我们定义出两个事件,分别为WorldChangedEvent和StyleChangedEvent。两个事件都继承自PubSubEvent。PubSubEvent是一个泛型类,可以在EventAggregator中被订阅和发布。
public