事件类型定义:
//定义事件委托 public delegate void EventHandler(object sender,EventArgs e); public class Button: Control { //函数绑定(即俗称的事件) public event EventHandler Click; //事件处理(raise method) protected void OnClick(EventArgs e) { if (Click != null) Click(this, e); } }
事件驱动模型
事件驱动模型是软件系统平台中的一个重要区域,现代软件系统大量地使用事件驱动的处理方法,尤其在用户界面方面。虽然如此,过去在软件开发语言中一直没有融入事件处理的因子,直到.NET的出现,才将事件处理的工作负荷一部分的分派给编译器,从而稍微减轻开发者的负担。
下图显示事件模型的组成份子:
Subscriber需事先和publisher预订要接受其发布的某事件(下图a1),publisher在某事件发生以后,必需先生成该事件的相关数据对象(下图a2.1),然后通过方法调用来通知subscriber(下图a2.2),也就是用回调(callback)的方式来通知subscriber。当然在预订的时候,并不一定要由subscriber自身来预订,也可以由另一个对象来帮忙预订。其动态图形示意如下:
本文并不探讨异步的信息传送,也就是在整个事件的处理过程当中,publisher和subscriber 对象皆需要同时存在。如果对于离线(offline)的方式来处理事件有兴趣的话,请参阅Java的JMS(Java Message Service)和.NET的LCE(Loosely Coupled Events)。
事件是什么?
那么,到底事件是什么?在软件系统中要如何表达一个事件?一个事件应该包括两个东西:识别事件的名称(event identity),和事件的相关的数据(event data)。例如,一个键盘按键被按下的事件可能叫KeyPressedEvent,事件数据则为该按键的代码。
先前提到发布事件是用调用方法的方式(回调),不过有一个问题,就是publisher无法事先知道subscriber的类型。在Java的编码模式当中,回调可以使用接口模式,也就是publisher必需事先定义好一个在发布事件中使用的接口,subscriber实现该接口中的方法,publisher则通过调用接口中的方法来完成发布事件的工作。如下图:
这样,在Java的编码模式中,一个事件的识别名称就是接口名称和其中的方法名称,而事件数据则自然是接口方法的参数了。Java对于这个接口的命名风格为XXXListener,顾名思义就是某事件的倾听者。例如:
public interface KeyListener extends EventListener { public void keyTyped(KeyEvent e); public void keyPressed(KeyEvent e); public void keyReleased(KeyEvent e); } |
由于一个接口中可以包含多个方法,所以Java在设计事件的时候,是将一组相关联的事件放在一起,这样设计的优点是可以很好的将事件做分类,并且在publisher中如果要处理的事件较多的话,可以使用比较少的成员变量来记录subscribers。缺点是如果subscriber只对事件接口中的部分事件有兴趣,也必需要全盘实现该接口(所以在AWT里有java.awt.event.XXXAdapter抽象辅助类)。另一个缺点则是必需要为每一类事件定义一个接口类型,即使可能大部分的事件只有极少的方法。
微软在为C#语言命名的时候,就刻意隐喻C#是从C/C++为基础发展而得的面向对象程序语言,始祖绝不是Java,所以肯定要保留一些C/C++的语言机制。在C/C++里面对回调的设计方式就是用函数指针,想当然C#也希望直接使用类似函数调用的方式来做为事件发布的方法。如下图:
所以C#期望使用函数指针类型来作为事件的识别名称,然后用函数的参数来传递事件数据。我们先用一段C++代码来描绘这幅图画:
Event type definition: // 定义KeyPressedCallback 为一个函数指针的类型, // 该函数接受一个整数型参数,无返回值 typedef void (*KeyPressedCallback)(int keyCode); Publisher: class Publisher { public KeyPressedCallback KeyPressedSink = null; ... void FireEvent(int KeyCode) { if (KeyPressedSink != null) (*KeyPressedSink)(keyCode);//callback } } Subscriber: void KeyPressedHandler(int keyCode) { ... } ... Publisher publisher = new Publisher(); //register publisher.KeyPressedSink = &KeyPressedHandler; |
一个当代的纯面向对象程序语言,是肯定希望要把造成程序复杂和不易维护的指针给去除的。所以在C#语言机制当中,势必要创造新的元素来取代,于是delegate(委托)出现了。如下:
Event type definition: // 定义KeyPressedDelegate 为一个类似函数指针的类型, // 该函数接受一个整数型参数,无返回值 delegate void KeyPressedDelegate(int keyCode); Publisher: class Publisher { public KeyPressedDelegate KeyPressed = null; ... void FireEvent(int KeyCode) { if (KeyPressed != null) KeyPressed(keyCode); } } Subscriber: void KeyPressedHandler(int keyCode) { ... } ... Publisher publisher = new Publisher(); //register publisher.KeyPressed = KeyPressedHandler; |
一开始,你可以把KeyPressedDelegate当成是与函数指针相类似的东西,通过它你可以引用一个实例方法或静态方法,就好像引用一个对象一样。然后可以通过这个delegate直接调用其引用的方法。但是下面你会看到delegate更扩大了其引用能力。
事件的预订和发布
Publisher必需能够接受多个subscribers的预订,所以在publisher当中必需维护预订者的列表以供将来发布事件使用。在Java语言的模式中,并没有提供特别的东西来帮助这件事,可以自己用collection 类来做,例如可以使用ArrayList 对象来做记录。Java的预订方法名的风格为addXXXListener(),因为在publisher端必需把subscriber 对象“添加”到预订者列表后面,如下图:
对于subscriber来说,预订动作的内部处理是黑箱的,subscriber不用关心publisher是如何做预订记录的。参考以下代码片段:
class Publisher { private ArrayList listenerList = new ArrayList(); public void addKeyListener(KeyListener l) { listenerList.add(l); } public void fireKeyPressedEvent(int keyCode) { Iterator iter = listenerList.iterator(); while (iter.hasNext()) { KeyListener l = (KeyListener)iter.next(); l.keyPressed(keyCode); } } } |
当然这段代码只是简单的示意,如果要考虑多线程的安全问题,可能要在addKeyListener()前面加上synchronized;还有,有预订就必然相应的有“退订”(removeXXXListener()),在这里就不再把它写出来。
如果每个publisher都要这样重复撰写这样的代码的确很麻烦。所以在.NET中势必希望能够提供一种用来帮助publisher记录预订者,和发布事件的工具。按一般设计者的初步想法,一定是先提供一个辅助类来协助:
从语法上考虑简化,add/remove动作应该可以用C++的operator=()、operator+=()和operator-=()来完成。像这样:
Publisher publisher = new Publisher(); ... publisher.KeyEventHandlerDelegate += KeyPressedHandler; //等同于 //publisher.EventHandlerDelegate.add(KeyPressedHandler); |
如果可以这样撰写的话,确实很简单。不过在强制性类型(strongly-typed)语言系统中,必需精确的定义add()方法参数中的delegate 类型,这样似乎无法写出一个可以公用的基础类。所以在.NET中,是结合delegate关键字,通过简单的语法,借助编译器来帮我们自动生成相关的代码。于是把delegate的能力再予以加强了:
Event type definition: public delegate void KeyPressedDelegate(int keyCode); Publisher: class Publisher { public KeyPressedDelegate KeyPressed; ... void FireKeyPressedEvent(int KeyCode) { if (KeyPressed != null) //依次调用记录在KeyPressed中的所有方法 KeyPressed(keyCode); } } Subscriber: void OnKeyPressed(int keyCode) { ... } void OnKeyPressed2(int keyCode) { ... } ... Publisher publisher = new Publisher(); publisher.KeyPressed = OnKeyPressed; //预订 publisher.KeyPressed += OnKeyPressed2; //预订另一个 |
这样一个delegate不仅可以帮忙记录一个以上的subscribers,也可以简单的通过一行的调用语句来发布事件。其实编译器会为每一个delegate 生成一个相应的类来帮助处理这些工作,不过是作为一个只是编写应用系统的程序员是不必要去了解这些细节的,有兴趣的人可以去研究System.Delegate和System.MulticastDelegate 类。
上面看到的结果应该已经是比较满意的,但是仍有改善空间。首先,因为一个delegate成员是public的,任何人都可以任意的直接接触,有失面向对象世界中的信息封装和隐藏(information encapsulation and hiding)的原则。所以在C#中又增加一个关键字“event”,用在放在声明一个delegate成员变量的前面,这样表示只有在声明这个delegate的类内部才可以直接对它进行subscriber 调用。
public delegate void KeyPressedDelegate(int keyCode); class Publisher { public event KeyPressedDelegate KeyPressed; ... void FireKeyPressedEvent(int KeyCode) { if (KeyPressed != null) //只有在Publisher才可以 KeyPressed(keyCode); } } // outside of Publisher... Publisher publisher = new Publisher(); // !!! 不允许 !!! 会编译错误 !!! publisher.KeyPressed(100); |
接着,event delegate是以一个成员变量的方式存在,如果能以属性的方式让外界进行存取,不是更好吗。于是又增加了event accessors。在C#语言中,是使用add和remove来封装实际的 += 和 -= 操作。如下:
class Publisher { protected event KeyPressedDelegate m_KeyPressed; // event accessor。定义一个事件属性。 public event KeyPressedDelegate KeyPressed { add { m_KeyPressed += value; } remove { m_KeyPressed -= value; } } void FireKeyPressedEvent(int KeyCode) { if (KeyPressed != null) m_KeyPressed(keyCode); } } |
不管是事件变量或者是事件属性,对声明事件变量和属性的类的外部,只能对它做 += 和 -= 操作。这样可以加强它的安全性。当然event accessor只有add和remove操作,所以不管是任何人(包括声明该事件属性的类内部),也只能对事件属性做 += 和 -= 操作。
经过这样的改善,可以理论上更减弱publisher和subscriber之间的耦合力了。
事件数据
接下来我们谈一谈另一个在事件模型中的重要角色,就是在事件发布中被传递的“事件数据”。
一个subscriber在接受同一种事件的时候,可能来自不同的publisher,所以自然地希望知道发出事件的人是谁,也就是在传递的参数当中,必需包含一个publisher 对象的引用。在Java中,推荐所有的事件数据类都继承java.util.EventObject 类。因为在生成一个EventObject 对象的时候,必需给一个event source 对象作为参数。然后可以通过EventObject的getSource()方法来取得这个对象。在EventObject里面,并没有包含其他任何事件数据,所以如果在事件的传递过程当中,有任何事件数据需要传递,就必需从EventObject 派生出一个新的子类出来。如下图:
在.NET当中也有一个相似的类叫System.EventArgs,但是这个类的内容是空的,如下:
public class EventArgs { public static readonly EventArgs Empty; static EventArgs() { Empty = new EventArgs(); } public EventArgs() {} } |
.NET认为不一定所有的subscriber都对event source感兴趣,所以如果需要的话,就把event source当成是delegate方法的参数来传递好了。.NET定义了一个标准的delegate EventHandler,以下是它的签名(signature):
public delegate void EventHandler(object sender, EventArgs e); |
以后,只要你需要的delegate的签名与EventHandler相同的话,就直接用它了。这里所谓的签名相同,是指参数的类型和返回值的类型皆相同。
Java和.NET都希望用户在定义的事件数据类的时候,尽可能的使用推荐的基类,因为这样在publisher对发出的事件数据内容有所变更或扩大的时候,对subscriber的冲击会比较小,这是由于多型(polymorphism)机制的帮助。
结束语
经过这番解析之后,应该能够比较清楚的了解到Java和.NET事件处理框架的设计思路,希望有助于读者更进一步理解其框架的形成过程。从语言的角度来看,.NET的确有一些针对性的改善和试图简化对事件的处理,Java则仍保有其一贯简约的风格。
总结:
本篇是较早期的一篇文章,转贴过来。从今日的角度看,事件类型的定义显然存有提高的空间。
发表评论
-
CRX几个快捷
2010-08-02 02:17 847double tab CTRL+~ -
NOOO:不仅仅是OO
2010-05-08 13:53 919Not Only OO--解耦及其于OO的现实意义 对于现在 ... -
XAML@WPF
2010-03-17 17:41 879绪, XAML本身的意义非常广泛。但在目前的程序模式下,主要 ... -
.net中的序列化与流
2010-03-15 16:41 806在.NET中,抽象基类System.IO.Stream代表流, ... -
关于“反射”的实现
2010-03-09 18:40 626在C#中,反射对于单个对象实现了类环境中的层次检索;对于相关的 ... -
关于“回调”的实现
2010-02-10 11:14 777callback基础: 回调机制包括带委托的成员、虚拟化的成 ... -
c#,JUST a language
2010-02-04 20:44 01、语言,是模式的基本解决因子,所以学模式with c++/j ... -
non-oo 2 oo
2009-09-20 01:50 783从非OO的IDE环境,乍转到.NET的c#环境下时,最大的真空 ...
相关推荐
### QT的事件处理机制 #### 一、概述 在探讨QT的事件处理机制之前,我们需要先理解事件在软件开发中的重要性。事件是程序与用户的交互桥梁,它们能够捕获用户的动作并触发相应的处理逻辑。在图形用户界面(GUI)应用...
为了让初学者更好理解Java事件处理机制及其编程技术,黄增喜、王晓明和于春三位作者提出了一个基于核心概念解析、事件处理流程分析以及角色分工清晰化的教学思路,并给出了相应的教学方法。以下是对这些知识点的详细...
C#事件处理机制是.NET框架中一个核心特性,它为对象间的通信提供了安全、高效的方式。在C#中,事件处理机制主要涉及到委托(Delegate)和事件(Event)两个概念,它们是事件驱动编程的基础。 首先,我们来理解什么...
### Java GUI 编程中的事件处理机制详细讲解 在Java GUI编程中,事件处理机制是构建用户界面交互的核心部分。本文将围绕“Java GUI编程中的事件处理机制详细讲解(1)”这一主题展开,深入探讨组件类事件、动作类...
在实际开发中,可以通过分析模态对话框的消息处理机制来处理类似的程序设计需求,甚至可以将文档/视图框架结构中的某些功能集成到对话框中,或者反之。这为程序设计提供了极大的灵活性。 模态对话框的消息处理机制...
文档单片机窗口化事件处理机制的理论分析与实现 在单片机领域中,事件处理机制是非常重要的一部分。然而,传统的单片机事件处理机制存在一些不足之处,如顺序性程序难以使用、逻辑顺序混乱等。为了解决这些问题,...
以太网组播中二层事件处理机制的分析 一、以太网组播中二层事件的描述 以太网经常会产生各种二层事件,交换机需要对这些事件进行处理。二层事件可以分为两类:普通二层事件和环路中的链路改变事件。普通二层事件...
Java事件处理机制分析 Java是一种面向对象、采用事件驱动机制的程序设计语言,掌握Java的事件处理机制是编写人机交互的图形用户界面程序的关键。本文对Java的事件处理机制进行了深入剖析,阐述了Java的事件处理模式...
C#事件处理机制是.NET框架中一个核心特性,它为对象间的通信提供了安全、高效的方式。在C#中,事件处理机制主要涉及到委托(Delegate)和事件(Event)两个概念,它们是事件驱动编程的基础。 首先,我们来理解委托...
"分析浏览器底层事件循环处理机制.pdf" 事件循环处理机制是浏览器底层的一个核心机制,它负责处理各种消息事件的处理逻辑。随着互联网的发展和网络速度的提高,基于C/S架构的桌面应用程序基本上都迁移到基于互联网...
* java事件处理机制在实际应用中的例子和分析 六、结论 项目教学法是一种非常有效的教学方法,它可以让学生更好地理解和掌握知识,并提高学生的实践能力和解决问题的能力。通过java事件处理机制的教学,我们可以让...
下面我们将深入探讨Android事件处理机制的原理及其源码分析。 首先,Android事件处理主要通过事件分发链来完成。当用户在屏幕上进行操作时,如点击按钮或滑动屏幕,这些动作会被转换为触摸事件并由系统进行处理。...
### Linux内核中的异常处理机制分析 #### 引言 在深入探讨Linux内核的异常处理机制之前,我们首先简要回顾一下Windows下的结构化异常处理(Structured Exception Handling,简称SEH)。SEH是Windows系统中用于处理...
在Android应用开发中,事件处理机制是至关重要的组成部分,它涉及到用户与应用程序的交互,包括点击、滑动等各类操作。本示例"Android 事件处理机制 demo"将深入探讨这一主题,通过实际代码演示如何有效地管理和响应...
源码分析对于理解进程错误处理机制也非常重要。通过阅读操作系统内核或库函数的源代码,我们可以看到如何定义和处理各种错误情况,以及如何实现上述的各种机制。 在"chapter6"这个文件中,可能包含了关于进程错误...
智能电网系统已成为我国城市建设和企业...依据智能电网的发展现状,以济宁市市中区和高新区供电系统为例,阐述了智能电网系统设计,分析了配网在发生故障时,智能配网系统处理的机制,提出了智能配网急需完善和改进的功能。
### Java和.NET事件处理机制的比较研究 #### Java事件处理机制 Java的事件处理机制主要采用了**观察者模式**下的**委派事件模型**(Delegation Event Model, DEM)。在这个模型中,事件处理过程涉及到了三个核心概念...
poll机制分析 poll机制是操作系统中的一种I/O多路复用机制,允许程序同时监视多个文件描述符的I/O事件。韦东山老师的文档对poll机制进行了详细的分析。 在Linux操作系统中,poll机制对应的系统调用是sys_poll,...
Flex事件机制是Adobe Flex应用程序中处理用户交互和组件通信的核心组成部分。它主要涉及事件流、事件处理程序以及事件对象的概念,这些在构建富互联网应用程序(RIA)时扮演着至关重要的角色。下面将对这些概念进行...
### WindML事件驱动机制分析 #### 一、嵌入式系统与WindML的背景 随着多媒体信息技术、互联网技术、通信技术以及消费类电子产品智能化趋势的不断加强,嵌入式系统因其特有的优势——占用内存少、高性能、具有完全...