论坛首页 Java企业应用论坛

Java事件模型的讨论

浏览 16901 次
该帖已经被评为精华帖
作者 正文
   发表时间:2003-11-09  
什么是事件?
说白了就是一个对象(对象A)的状态改变了的时候,通知其他的对象(对象B)发生了这么一件事。

这里很自然就有两种模式:推/拉模式。
推者,状态改变的对象(A)通知其他对象(B)
拉者,其他对象(B)监听感兴趣的对象(A)--想想windows以前那个大大的switch

Java采用的大概是推模式,好像又称订阅/发布模式,就是B向A注册(实际就是把自己的引用复制一份给A),然后当A的状态改变时,调用B相关的方法(这就是为何要用set/get访问属性的原因了,方法可以嵌套调用,但属性无法调用方法,这也是为何标准要求我们只用set/get,而不直接访问属性的原因),达到通知状态改变的目的

JavaBean组件模型的威力在于和反射的结合,通过反射我们能动态地操纵bean的属性(按名字访问),加上事件机制(我们可以通过定制消息的走向来控制流程),这是一种很好的流程控制机制。但这种方式的问题是,当参与的对象一多,注册与撤销注册工作就会很麻烦(对象之间的耦合变大了),还得定制事件处理机制--等于是将部分业务逻辑写到bean里头去了。

这里就牵涉到一个流程是自控还是他控的问题。Struts等基于servlet的MVC框架是用他控,将流程控制放在java外面,swing/awt是使用事件机制,将流程控制放在java里面。这大概是为何事件机制在IDE之类的应用中非常常见,而在商业应用中比较罕见的原因吧。因为IDE是面向机器的,它的工作流程比较固定--想起以前windows下的事件就心有余悸:(
   发表时间:2003-11-10  
无明 写道
Java采用的大概是推模式,好像又称订阅/发布模式,就是B向A注册(实际就是把自己的引用复制一份给A),然后当A的状态改变时,调用B相关的方法(这就是为何要用set/get访问属性的原因了,方法可以嵌套调用,但属性无法调用方法,这也是为何标准要求我们只用set/get,而不直接访问属性的原因),达到通知状态改变的目的

JavaBean组件模型的威力在于和反射的结合,通过反射我们能动态地操纵bean的属性(按名字访问),加上事件机制(我们可以通过定制消息的走向来控制流程),这是一种很好的流程控制机制。但这种方式的问题是,当参与的对象一多,注册与撤销注册工作就会很麻烦(对象之间的耦合变大了),还得定制事件处理机制--等于是将部分业务逻辑写到bean里头去了。

你的理解有一些问题。Java 中的事件模型确实采用的是订阅/发布模式,与 MDB 的实现有些相似。但是事件与 JavaBean 是完全独立的概念,与 JavaBean 属性的 set/get 的命名约定无关。反射机制已经可以找到一个类中所有 public 的属性和方法。之所以有 set/get 的约定是为了便于应用程序构建工具更容易地找到 Bean 中的属性。
参与的对象(监听器)再多也不会引起问题,完全不会引起什么耦合变大的情况。因为调用注册和撤消操作的代码并不在事件源对象中,事件源的代码不需要做多少修改。当然添加了新的事件类型后要在事件源中增加 add...Listener() 和 remove...Listener()。但是如果你在开发前就规划好需要多少种的事件类型并且把事件源的代码写好,那么即使有更多的监听器,只要其监听的事件类型不超出事件源可支持的范围,那么事件源的代码是不需要做任何修改的。你在创建监听器时把事件源当作参数传给监听器,由监听器自己来注册。并且在监听器的 finalize() 方法中执行撤消操作。这样就不需要把注册和撤消监听器的代码写得到处都是了。
Bean 多数场合是作为事件源,容器作为监听器,不过 Bean 也可以作为监听器,或者同时做事件源和监听器。监听器本来就是要执行业务逻辑的,所以当 Bean 作为监听器时,其中肯定包含业务逻辑的代码。它不处理业务逻辑还可以做什么呢,我对你的思路有些不解。
无明 写道
这里就牵涉到一个流程是自控还是他控的问题。Struts等基于servlet的MVC框架是用他控,将流程控制放在java外面,swing/awt是使用事件机制,将流程控制放在java里面。

桌面应用与服务器端应用的开发思想有很大的差异,服务器端的开发更多的是基于一种反向控制(IOC)的思想,由容器负责其包含的所有对象全生命周期的管理。这样的设计主要是出于安全上的考虑。
引用
反向控制(Inversion of Control,IOC)的概念是指组件总是由外部进行管理的。这个短语是由Brian Foote在他的一篇论文中最先使用的。组件所需的一切通过 Contexts、Configurations 和 Loggers 的方式赋予组件。实际上,组件生命周期中的每个阶段都是由创建组件的代码所控制的。当您使用这种模式时,就实现了一种组件与您的系统安全交互的方法。
0 请登录后投票
   发表时间:2003-11-10  
这里有一个考虑是预期将来的事件源的变化多,还是监听的变化多。在事件源中的注册方法实际上就是将二者耦合起来了。一个思路采用command模式,所有监听器都集成一个统一command接口,无论如何增加监听器都不会导致事件源变化。同时所有事件源也都依赖与此command接口。这能够满足大多数情况,但满足不了所有情况。
对于部分业务逻辑写入bean,对于什么属于业务是有分歧的,在一些人眼里,这些算不上业务。为了使业务组件具有复用性,一般不会直接于监听器绑定,这就导致代理类的膨胀。一种可以借鉴的方案,是对于事务的处理,把事务定义转成配置,而不是硬编码到程序里面。
dlee 写道
桌面应用与服务器端应用的开发思想有很大的差异,服务器端的开发更多的基于一种反向控制(IOC)的思想,由容器负责其包含的所有对象全生命周期的管理。这样的设计主要是出于安全上的考虑。

采用ioc应该不是出于安全上的考虑,同时ioc本身也不能保证安全。ioc是体现框架作用的一个主要表现。
要自己写框架的化,这是一个好东东,spring是一个很好的样板,还有一个说自己是type3 ioc的pico,也是一个不错的东西。[/i]
0 请登录后投票
   发表时间:2003-11-10  
youcai 写道
为了使业务组件具有复用性,一般不会直接于监听器绑定,这就导致代理类的膨胀。

监听器和代理间可以构成组合关系,由代理来执行真正的业务逻辑处理(调用后端的业务组件)。但是在有些重用性要求不高的场合,在监听器中直接写业务逻辑也是可以的。
youcai 写道
采用ioc应该不是出于安全上的考虑,同时ioc本身也不能保证安全。ioc是体现框架作用的一个主要表现。

对的,我说的有问题,IOC 确实不能保证安全。IOC 减轻了应用开发的很多工作量,对象生命周期事务性管理代码对开发人员完全透明了,提高了开发效率。
0 请登录后投票
   发表时间:2003-11-11  
翻了一下JDK,重新整理了一下想法:
事件机制
参与者:监听器对象、事件源对象、事件对象

监听器对象->实现某特定监听器接口的类的实例->获得事件对象(参数),并根据事件作出反应->监听器对象的事件处理方法是由谁唤起(invoke)的?

事件源对象->注册监听器对象并向其发送事件对象->注册过程,监听器对象将自己reference复制给事件源对象,按Java的代码描述就是:object.addEventListener(eventListenerObject),或者说是事件源对象接受监听器对象的注册(订阅);发送事件对象过程(发布),事件是在哪里产生的?

前提:事件机制是与属性(Property)紧密相关的,那么开始讨论事件机制前,有必要回过头来看看属性。

JavaBean的属性就是类中的变量。按照JavaBean标准中,属性又细分为四类:Simple,Index,Bound与Constrained属性。
Simple和Index这两类属性很好理解,就不用理它了。
Bound属性是指当该种属性的值发生变化时,通知其它的对象,而当每次属性值改变时,就激发一个PropertyChange事件。
Constrained属性是指当这个属性的值要发生变化时,与这个属性已建立了某种连接的其它Java对象可否决属性值的改变。这种属性依赖于事件机制,是在Bound属性事件发生后才发生的,可以不用理它,集中精力在Bound属性上边。
JDK对Bound属性的支持是在Java.beans包中,我找了一下,把注意力集中在PropertyChangListener,PropertyChangeEvent, PropertyChangeSupport这三个类上。
PropertyChangListener继承自java.util.EventObject,这是个具体类(使用具体继承),用来定义事件,很简单,可见事件类就是简单的JavaBean(只有简单属性和set/get方法),只是用来携带数据的,可能还会带上个时间戳。

PropertyChangListener继承自java.util.EventListener,这是个空接口,继承这样的接口意味着这是定义代表事件种类的接口,有多少种事件就有多少个方法。PropertyChangListener接口只有一个,void propertyChange(PropertyChangeEvent evt);它只关心Property的改变。

真正干活的是PropertyChangeSupport,只有一个Bean使用了PropertyChangeSupport,那么它才可能具备事件传播能力。
PropertyChangeSupport也是个具体类,嗯,那么我们可以猜测,这可能是一个工具类。
首先看看它的数据成员:
transient private java.util.Vector listeners; //这是保存监听器reference的地方
private java.util.Hashtable children; //
private Object source;//需要获得事件支持的对象的reference
剩下两个数据成员与持久特性有关,不理它。
再看看构造函数:
public PropertyChangeSupport(Object sourceBean) {
if (sourceBean == null) {
    throw new NullPointerException();
}
source = sourceBean;
}
看来PropertyChangeSupport是用来加工简单bean的。

然后看看其他方法
主要是一堆:addPropertyChangeListener/ removePropertyChangeListener方法,是用来读写listeners和children的
事件是怎么发出去的?在public void firePropertyChange(String propertyName, Object oldValue, Object newValue);
里面有这么一段:
……
PropertyChangeEvent evt = new PropertyChangeEvent(source,
    propertyName, oldValue, newValue);
……
事件对象在这里产生,并将新旧属性封入事件对象,交由监听器对象自己处理:
……
target.propertyChange(evt);//target是监听器队列里的某个特定reference
……
propertyChange是什么?就是实现上面那个PropertyChangListener接口的一个对象,这里用了回调接口。这样监听器就收到事件对象了,可以干自己的活了。
这下有些明白为什么要那么多的Adapter和内部类了,因为我们无法确定是每种事件一个接口还是一个接口用于所有事件(这是Anders说的)。Java的事件模型实际上应包括事件源、监听器和协调者,只是这个协调者可以使用工具类或代码生成技术掩盖住了――我相信肯定有这样的JavaBean包装工具,BeanUtils或者就算是一个,那些Adapter和内部类也应该算是协调者吧。
关于我那set/get的想法,其实是想用它点燃导火索,调用firePropertyChange,这样就不必使用反射了。不过后来我查了查资料,原来Java的做法是扩展了反射,实现了内省,这样就不用拙劣地在set方法中插点东西了。至于这最开头的一段导火索是怎么点燃的恐怕又得找找反射和内省的资料了……
Anders说,“如今人们使用的OOP语言全是混合式的,……,对象在许多方面仍然只是具有一些方法和一个this指针的结构,……,对于属性,它们并不真正是属性,它们不过就是get某某东西,set某某东西。但当它们出现在一个属性检查器中时,它们就是某某东西了。而你只要知道那里进行的映射。”我现在有点明白他的意思了,
属性(property)可能仅仅是一种语法修饰,而没有得到真正的语言级支持

嗯,有点想看看C#了……
0 请登录后投票
   发表时间:2003-11-11  
无明 写道
监听器对象的事件处理方法是由谁唤起(invoke)的?

是由事件源在发生事件时唤起的。
无明 写道
事件是在哪里产生的?

是由事件源创建的啊。当你使用 Button 这些 GUI 组件时,其中已经封装了创建事件对象的代码。

你还是有些把事件与属性混在一起了,它们之间并没有必然联系。自己完全可以创建事件类型和事件监听器类型,并且在适当时机由事件源创建事件并发送给监听器。JavaBean 使用的就是标准的 Java 事件模型。任何 Java 类都可以称做 JavaBean,java.beans 包只是为了实现 JavaBean 的设计提供的工具包。如果只用简单属性(不涉及到事件),是完全不需要 java.beans 包中的工具的。
标准的 Java 事件模型只有 3 个组成部分:事件对象、事件源、事件监听器,不存在协调者这样一个概念。AWT 中的那些 Adapter 只是为了让你更容易地实现事件监听器接口。因为每种监听器接口可以监听多种事件。Adapter 中全部都是空方法,这样你从 Adaper 继承后就不需要实现监听器接口中所有的方法,只需要覆盖掉你感兴趣的事件类型对应的方法。

JavaBean 的属性应该没有语言级的支持。Java 语言规范中肯定没有这方面的定义。JavaBean 的属性跟普通的实例变量没有多大区别,只是有一对关联的 set/get 方法而已。应用程序构建工具(Beanbox,etc.)通过反射找到所有的 set/get 方法从而确定所有的属性。因为属性一般是 private  的,反射机制无法探知(若能探知,则违反了面向对象的封装原则),所以只能依靠 set/get 的约定来确定。
0 请登录后投票
   发表时间:2003-11-12  
>>是由事件源在发生事件时唤起的。
其实是在PropertyChangeSupport中唤起的

>>是由事件源创建的啊
其实是在PropertyChangeSupport中创建的


>>当你使用 Button 这些 GUI 组件时,其中已经封装了创建事件对象的代码。
是的,就是因为在GUI中封装的太多了,我才想到用javabean来跟踪,这样会简单一点

>>你还是有些把事件与属性混在一起了,它们之间并没有必然联系。
或者有属性不变的事件,但那就是纯粹的方法调用了,我感兴趣的是,类型的变化为何会导致方法的自动调用?是谁在观察着属性的变化?--在windows下我知道,在Java下我就不清楚了,现在仍然没弄清楚……(通过在set方法中插入点东西可以不观察,但Java好象没使用这种办法,而是用了反射)

不使用PropertyChangeSupport等工具类实现事件机制也可以,只要在事件源写类似的代码,这就不必了

>>标准的 Java 事件模型只有 3 个组成部分:事件对象、事件源、事件监听器,不存在协调者这样一个概念。
我觉得协调者部分被有意淡化了。对于使用者来说,只要使用beans中的类,或者别的更好的包装器--如IDE中的那些,是完全感觉不到协调者的。我用过类似的包装器,很好用,但让我搞不明白魔术是怎么变出来的了,所以才有了上面的思考。

>>那些 Adapter 只是为了让你更容易地实现事件监听器接口。
是的,这就是“采用command模式,所有监听器都集成一个统一command接口,无论如何增加监听器都不会导致事件源变化。”,这也是Anders在C#增加委托这一特性的原因。使用接口,我们得绕个弯才能到达目的地。他称之为“简单复杂性”--不是真的简单,只是看起来简单。
到底怎么才是真的简单,我也不清楚,只是,好奇心被激起来了,就有了上面那些东西:)


这些漂亮的名词后边到底是什么东西呢?我们是不是应该坐下来好好想想?
0 请登录后投票
   发表时间:2003-11-13  
无明 写道
是的,这就是“采用command模式,所有监听器都集成一个统一command接口,无论如何增加监听器都不会导致事件源变化。”,这也是Anders在C#增加委托这一特性的原因。使用接口,我们得绕个弯才能到达目的地。他称之为“简单复杂性”--不是真的简单,只是看起来简单。

呵呵,我的一本 O'Reilly《C# 精髓》放了一年多还没有看。
Anders 是大才啊,非我辈所能及。不过目前 Java 中的 Interface + Adaptor 我觉得也基本上足够了。
我记得 VJ++ 就是 Anders 设计的,开发起来还是很方便的,并不象某些人说的那么垃圾。只是 M$ 憎恶 Java,另外 Sun 抓着标准不放,要不大家都用 Java,就不用搞出什么 C# 和 .Net 了。
0 请登录后投票
   发表时间:2004-01-13  
powerplane 写道
好久没有写 Java 和 .Net 了,我不能把例子用最纯正的 Java 或者 .Net 风格来写出代码来。
我曾经研究过所谓事件模型一段时间。期间对比过 Java 根 .Net 的 delegate 事件模型。我也很菜,可能有错的。

1、Java 的
ObjA 向 ObjB 注册一个事件,其实无非是把 ObjA 的指针传给 ObjB,并且告诉 ObjB 事件发生的时候要执行什么函数。由于 Java 的事件模型中,是通过接口来做的,也就是什么事件对应的函数接口是规定好的。在此,假设是 SomeEvent.ActionPerform()

然后 ObjB 有个链表把这些指针都存放起来。

比如 ObjB 的一个成员值被更改了,比如一通过一个 SetValue() 函数更改了,那么就产生了一个事件。所谓事件不过是 SetValue() 中主动调用了一个 fireEvent() 的私有函数。
这个 fireEvent 作什么呢?就是把链表中的指针一个一个调出来,执行他们的对应的事件函数 SomeEvent.ActionPerform()

2、.Net 的。
.Net 用一个叫做代理的方法来事件,它专门有一个 delegate 的关键字。我想大概的原理根 Java 也是一样的。事隔太久,我举不出例子。我当时的结论是每一个 .Net 类天生都具有了处理事件的能力,他们却不用管理一个数组或者链表来存放监听对象的指针。这个估计是不是编译器或者.Net 内部进行了处理了。

其实 delegate 关键字,不是第一次出现,早在 MS 的 Visual J++ 的时候就引入了这个机制。据说最早起源是 delphi 的闭包。 Sun 很反对这个 delegate机制,认为破坏了 java 规范; MS 就坚持 说 delegate 减少了粘和代码(glue code)。大家有兴趣,估计用 google 或者 MSDN 也能搜索到。
使用delegate实现events很舒服的
这里有个简单额例子
//Step1. Define the AlarmEventArgs class
//Step 2. Delegate declaration.
//
public delegate void AlarmEventHandler(object sender, AlarmEventArgs e);

// Class definition.
//
public class AlarmClock
{
//Step 3. The Alarm event is defined using the event keyword.
//The type of Alarm is AlarmEventHandler.
  public event AlarmEventHandler Alarm;
//
//Step 4. The protected OnAlarm method raises the event by invoking
//the delegates. The sender is always this, the current instance of
//the class.
//  
protected virtual void OnAlarm(AlarmEventArgs e)
   {
    if (Alarm != null)
     {
       //Invokes the delegates.
       Alarm(this, e);
      }
   }
}
2 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics