HTML5技术

事件总线(Event Bus)知多少 - 『圣杰』(2)

字号+ 作者:H5之家 来源:H5之家 2017-06-09 14:01 我要评论( )

事件源应该至少包含事件发生的时间和触发事件的对象。 我们提取IEventData接口来封装事件源: /// summary/// 定义事件源接口,所有的事件源都要实现该接口/// /summarypublic interface IEventData{/// summary///

事件源应该至少包含事件发生的时间和触发事件的对象。
我们提取IEventData接口来封装事件源:

/// <summary> /// 定义事件源接口,所有的事件源都要实现该接口 /// </summary> public interface IEventData { /// <summary> /// 事件发生的时间 /// </summary> DateTime EventTime { get; set; } /// <summary> /// 触发事件的对象 /// </summary> object EventSource { get; set; } }

自然我们应该给一个默认的实现EventData:

/// <summary> /// 事件源:描述事件信息,用于参数传递 /// </summary> public class EventData : IEventData { /// <summary> /// 事件发生的时间 /// </summary> public DateTime EventTime { get; set; } /// <summary> /// 触发事件的对象 /// </summary> public Object EventSource { get; set; } public EventData() { EventTime = DateTime.Now; } }

针对Demo,扩展事件源如下:

public class FishingEventData : EventData { public FishType FishType { get; set; } public FishingMan FisingMan { get; set; } }

完成后,我们就可以去把在FishingRod声明的委托参数类型改为FishingEventData类型了,即public delegate void FishingHandler(FishingEventData eventData); //声明委托;
然后修改FishingMan的Update方法按委托定义的参数类型修改即可,代码我就不放了,大家自行脑补。

到这一步我们就统一了事件源的定义方式。

3.2.提取事件处理器

事件源统一了,那事件处理也得加以限制。比如如果随意命名事件处理方法名,那在进行事件注册的时候还要去按照委托定义的参数类型去匹配,岂不麻烦。

我们提取一个IEventHandler接口:

/// <summary> /// 定义事件处理器公共接口,所有的事件处理都要实现该接口 /// </summary> public interface IEventHandler { }

事件处理要与事件源进行绑定,所以我们再来定义一个泛型接口:

/// <summary> /// 泛型事件处理器接口 /// </summary> /// <typeparam name="TEventData"></typeparam> public interface IEventHandler<TEventData> : IEventHandler where TEventData : IEventData { /// <summary> /// 事件处理器实现该方法来处理事件 /// </summary> /// <param name="eventData"></param> void HandleEvent(TEventData eventData); }

你可能会纳闷,为什么先定义了一个空接口?这里就留给自己思考吧。

至此我们就完成了事件处理的抽象。我们再继续去改造我们的Demo。我们让FishingMan实现IEventHandler接口,然后修改场景类中将fishingRod.FishingEvent += jeff.Update;改为fishingRod.FishingEvent += jeff.HandleEvent;即可。代码改动很简单,同样在此略去。

至此你可能觉得我们完成了对Demo的改造。但事实上呢,我们还要弄清一个问题——如果这个FishingMan订阅的有其他的事件,我们该如何处理?
聪颖如你,你立马想到了可以通过事件源来进行区分处理。

public class FishingMan : IEventHandler<IEventData> { //省略其他代码 public void HandleEvent(IEventData eventData) { if (eventData is FishingEventData) { //do something } if(eventData is XxxEventData) { //do something else } } }

至此,这个模式实现到这个地步基本已经可以通用了。

4. 实现事件总线

通用的发布订阅模式不是我们的目的,我们的目的是一个集中式的事件处理机制,且各个模块之间相互不产生依赖。那我们如何做到呢?同样我们还是一步一步的进行分析改造。

4.1.分析问题

思考一下,每次为了实现这个模式,都要完成以下三步:

虽然只有三步,但这三步已经很繁琐了。而且事件发布方和事件订阅方还存在着依赖(体现在订阅者要显示的进行事件的注册和注销上)。而且当事件过多时,直接在订阅者中实现IEventHandler接口处理多个事件逻辑显然不太合适,违法单一职责原则。这里就暴露了三个问题:

带着问题思考,我们就会更接近真相。

想要精简步骤,那我们需要寻找共性。共性就是事件的本质,也就是我们针对事件源和事件处理提取出来的两个接口。

想要解除依赖,那就要在发布方和订阅方之间添加一个中介。

想要避免订阅者同时处理过多事件逻辑,那我们就把事件逻辑的处理提取到订阅者外部。

思路有了,下面我们就来实施吧。

4.2.解决问题

本着先易后难的思想,我们下面就来解决以上问题。

4.2.1. 实现IEventHandler

我们先解决上面的第三个问题:如何避免在订阅者中同时处理多个事件逻辑?

自然是针对不同的事件源IEventData实现不同的IEventHandler。改造后的钓鱼事件处理逻辑如下:

/// <summary> /// 钓鱼事件处理 /// </summary> public class FishingEventHandler : IEventHandler<FishingEventData> { public void HandleEvent(FishingEventData eventData) { eventData.FishingMan.FishCount++; Console.WriteLine("{0}:钓到一条[{2}],已经钓到{1}条鱼了!", eventData.FishingMan.Name, eventData.FishingMan.FishCount, eventData.FishType); } }

这时我们就可以移除在FishingMan中实现的IEventHandler接口了。
然后将事件注册改为fishingRod.FishingEvent += new FishingEventHandler().HandleEvent;即可。

4.2.2. 统一注册事件

上一个问题的解决,有助于我们解决第一个问题:如何精简流程?
为什么呢,因为我们是根据事件源定义相应的事件处理的。也就是我们之前说的可以根据事件源来区分事件。
然后呢?反射,我们可以通过反射来进行事件的统一注册。
在FishingRod的构造函数中使用反射,统一注册实现了IEventHandler<FishingEventData>类型的实例方法HandleEvent:

public FishingRod() { Assembly assembly = Assembly.GetExecutingAssembly(); foreach (var type in assembly.GetTypes()) { if (typeof(IEventHandler).IsAssignableFrom(type))//判断当前类型是否实现了IEventHandler接口 { Type handlerInterface = type.GetInterface("IEventHandler`1");//获取该类实现的泛型接口 Type eventDataType = handlerInterface.GetGenericArguments()[0]; // 获取泛型接口指定的参数类型 //如果参数类型是FishingEventData,则说明事件源匹配 if (eventDataType.Equals(typeof(FishingEventData))) { //创建实例 var handler = Activator.CreateInstance(type) as IEventHandler<FishingEventData>; //注册事件 FishingEvent += handler.HandleEvent; } } } }

这样,我们就可以移出场景类中的显示注册代码fishingRod.FishingEvent += new FishingEventHandler().HandleEvent;。

4.2.3. 解除依赖

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • [问题贴]mui.openWindow+自定义事件监听操作让alert()执行两次 - CN_Simo

    [问题贴]mui.openWindow+自定义事件监听操作让alert()执行两次 - CN_

    2017-05-27 10:02

  • HTML5支持服务器发送事件(Server-Sent Events)-单向消息传递数据推送(C#示例) - 熊仔其人

    HTML5支持服务器发送事件(Server-Sent Events)-单向消息传递数据推送

    2017-05-19 11:02

  • HTML5中的新事件 - zxyGo

    HTML5中的新事件 - zxyGo

    2017-03-10 14:00

  • 移动端tap事件的封装 - 一混五六年

    移动端tap事件的封装 - 一混五六年

    2017-02-28 18:00

网友点评