.NET中把“事件”看作一个基本的编程概念,并提供了非常优美的语法支持,对比如下C#和Java代码可以看出两种语言设计思想之间的差异。

// C#
someButton.Click += OnSomeButtonClick;
// Java
someButton.addActionListener(
new ActionListener(){
public void actionPerformed(){
...
}
});

在我们的软件中就大量使用事件来对监听者与发布者解耦,但也遇到了一些局限,在这里跟大家分享一二。一是无法保证监听者的调用顺序;二是当监听者很多时的监听、解除监听的效率问题。

事件监听者的调用顺序

.NET的事件监听机制对监听者的调用顺序没有明确的保证,但有时我们却要求保证不同组件之间的处理顺序。比如,在我们的软件中使用类似解释器模式的方式来实现用户交互操作,一个称作交互源的组件负责将UI控件上的事件分派给一组称为交互器的组件,这些组件依照事先确定的优先级依次获得事件处理的机会,只有当具有高优先级的交互器没有处理事件时,低优先级的组件才能执行进一步的处理。这样,我们就能在不同业务功能的实现中通过以不同的顺序组织交互器来重用它们。比如,重用一些基本的视图缩放、平移、菜单处理等功能。

在上述场景下,如何保证交互器间事件处理的顺序就变得很重要了。当然如果你看一下MulticastDelegate的源代码的话,可以知道在当前的实现中其实各个监听者还是有一定的调用顺序的。但一来这属于实现细节,在将来完全可能改变;二来如果不同的监听器位于不同的模块中时,要依赖于这一实现而保证它们之间的调用顺序也是很困难的。

在这里我们借鉴了Java中以接口进行事件处理的方式,并在添加监听器的同时接收一个表示优先级的参数,这样就可以明确的维护各个监听器的顺序了,如下面的代码所示。我们在交互器(IInteractor)接口中为每一个UI事件定义了相应的方法,并且让InteractSource负责将控件上的事件转化为对接口中相应方法的调用。

public class InteractSource
{
public void AddInteractor(int priority, IInteractor interactor)
{
}
} public interface IInteractor
{
public void OnMouseDown(MouseEventArgs e)
{
} ... ...
}

监听器添加与移除的效率

MulticastDelegate是我们平常使用的事件(event)机制背后的实现,通过其源代码可以看到,它在内部使用数组保存了对各个监听器的引用。这就会造成一个问题——当对一个事件的监听器数目很多时,添加和移除监听器的效率将会变得非常低。以移除为例,对于有N个监听器的事件来说,平均要进行N/2次比较才能确定监听器的位置,而且还要有额外的数组整理操作。为了解决这一情况,我们先是尝试自行定义事件的添加、移除逻辑,并在内部尝试使用字典、哈希表等多种方式进行存储,但事实证明,虽然二者在时间复杂度上有优势,不过其实际效率还是达不到要求。

最好状态下是要有一种能在常数时间内添加和移除监听器的数据结构,也许你也想到了——双向链表。

也许你又想到了——在双向链表中添加和删除是常数时间,但查找却仍然是O(n)的复杂度。

使用接口形式的设计方式再次展现了其灵活性,我们可以将事件发布者的设计为如下形式(示意代码):

public class EventSource
{
private LinkedList list = new LinkedList(); public Tocken AddListener(IEventListener listener)
{
LinkedListNode n = new LinkedListNode(listener);
list.AddLast(n);
return new Tocken(node);
} public void RemoveListener(Tocken tocken)
{
list.Remoe(tocken.node);
} public class Tocken
{
internal LinkedListNode node;
}
}

在此类中使用双向链表存储已经添加的监听器,而在AddListener方法每次调用时都将所添加的链表节点保存到一个令牌(Token)中返回。监听者需要保存这个令牌,并使用它来解除监听。当然,监听者完全可以忽略令牌是个什么东西,就像地铁票从来就是只是一张票而已,我们不曾关心它包含着什么信息。不过对于发布者来说却可以将一些定位信息保存在其中,从而在解除监听时充分利用,在上面的代码中我就保存了链表节点的引用,从而达到监听者的添加、定位、移除都在常数时间内完成。

当然,还可以在Tocken中保存发布者的引用,这样就可以发现”取消对一个从来没有监听过的对象的监听“这样的BUG。或者,还有其它信息。

.NET事件监听机制的局限与扩展的更多相关文章

  1. 4.JAVA之GUI编程事件监听机制

    事件监听机制的特点: 1.事件源 2.事件 3.监听器 4.事件处理 事件源:就是awt包或者swing包中的那些图形用户界面组件.(如:按钮) 事件:每一个事件源都有自己特点有的对应事件和共性事件. ...

  2. 关于事件监听机制的总结(Listener和Adapter)

    记得以前看过事件监听机制背后也是有一种设计模式的.(设计模式的名字记不清了,只记得背后实现的数据结构是数组.) 附上事件监听机制的分析图: 一个事件源可以承载多个事件(只要这个事件源支持这个事件就可以 ...

  3. GUI编程笔记(java)05:GUI事件监听机制原理和举例说明

    1.事件监听机制:       A:事件源          事件发生的地方       B:事件             就是要发生的事情       C:事件处理       就是针对发生的事情做 ...

  4. JAVA事件监听机制学习

    //事件监听机制 import java.awt.*; import java.awt.event.*; public class TestEvent { public static void mai ...

  5. Java中的事件监听机制

    鼠标事件监听机制的三个方面: 1.事件源对象: 事件源对象就是能够产生动作的对象.在Java语言中所有的容器组件和元素组件都是事件监听中的事件源对象.Java中根据事件的动作来区分不同的事件源对象,动 ...

  6. java Gui编程 事件监听机制

    1.     GUI编程引言 以前的学习当中,我们都使用的是命令交互方式: 例如:在DOS命令行中通过javac java命令启动程序. 软件的交互的方式:   1. 命令交互方式    图书管理系统 ...

  7. Java swing(awt):事件监听机制的实现原理+简单示例

    (1)实现原理 事件监听机制的实现: 参考图:事件模型_ActionEvent 为了节省资源,系统无法对某个事件进行实时的监听.故实现的机制是当发生某个事件后,处理代码将被自动运行,类似钩子一般.(回 ...

  8. JAVA之旅(三十一)——JAVA的图形化界面,GUI布局,Frame,GUI事件监听机制,Action事件,鼠标事件

    JAVA之旅(三十一)--JAVA的图形化界面,GUI布局,Frame,GUI事件监听机制,Action事件,鼠标事件 有段时间没有更新JAVA了,我们今天来说一下JAVA中的图形化界面,也就是GUI ...

  9. 简单剖析Node中的事件监听机制(一)

    使用js的class类简单的实现一个事件监听机制,不同于浏览器中的时间绑定与监听,类似于node中的时间监听,并且会在接下来的文章中去根据自己的理解去写一下Event模块中的原理. Node.js使用 ...

随机推荐

  1. 传参数应该用哪种形式——值、引用、指针?

    类型:C++ & Qt4,创建时间:十二月 30, 2011, 7:43 p.m. 标题无"转载"即原创文章,版权所有.转载请注明来源:http://hgoldfish.c ...

  2. Codeforces Round #382 Div. 2【数论】

    C. Tennis Championship(递推,斐波那契) 题意:n个人比赛,淘汰制,要求进行比赛双方的胜场数之差小于等于1.问冠军最多能打多少场比赛.题解:因为n太大,感觉是个构造.写写小数据, ...

  3. F#之旅9 - 正则表达式

    今天,cozy群有个群友发了条正则,问正则匹配相关的问题.虽然他的问题用html selector去处理可能更好,但是我也再一次发现:我忘了正则怎么写的了! 忘掉正则是有原因的,这篇文章会简单记录下F ...

  4. 安卓开发error opening trace file: No such file or directory (2)报错原因

    error opening trace file: No such file or directory (2) 这个问题的出现是因为运行的测试机android系统版本和项目api不一致导致. 改成一样 ...

  5. datatable 加序号列

    最近使用datatable时,发现没有像jqgrid那样生成序号列,在国外网站搜罗了一下还是很简单的,就要在aoColumns中添加一空列占位就行,然后再用fnRowCallback添加序号 示例如下 ...

  6. iOS Hit-Test应用

    最近又看了遍苹果的官方文档<Event Handling Guide for iOS>,对事件响应链中的hit-test view 又多了些理解,个人觉的官方文档对这块讲的非常简单,很多东 ...

  7. LeetCode 412. Fizz Buzz

    Problem: Write a program that outputs the string representation of numbers from 1 to n. But for mult ...

  8. 禁用ViewPager边界滑动效果(转)

    反射设置方法 private EdgeEffectCompat leftEdge; private EdgeEffectCompat rightEdge; public void DisableLRS ...

  9. 转载:移动web开发规范

    本文来源:http://blog.csdn.net/joueu/article/details/44329825 以下是规范建议,均是日常在开发当中的的一些经验,仅供参考. 移动web开发规范 一.头 ...

  10. Android 利用RecyclerView.Adapter刷新列表中的单个view问题

    首先使用RecyclerView的adapter继承:RecyclerView.Adapter public class OrderListAdapter extends RecyclerView.A ...