1.从总体看事件机制
其实事件机制是一种处理世界的方式和方法。传统的顺序程序设计总是按照流程来安排所做的工作,而事件机制的特点在于:等待,如果有事情发生则处理之。这样的好处是顺序程序设计在没有事情做的时候也必须循环运行,做毫无效率的空的循环工作。而事件机制在没有事情的时候则可以不做任何事情的等待,从而可以释放各种资源用于其它需要的程序。其实,这和计算机的中断处理是一个想法和道理的。
事件总是和某个对象相关,即每个事件必须有一个事件源。比如,按纽按下事件必须和某个按钮相关,而绝对不会出现了一个按纽按下事件,但是没有任何按钮存在。
同时,某个事件发生了,则可能该事件会引起一些相关效果,也可能虽然发生,但不产生任何效果。即事件发生可能会被处理,也可能不会被处理。
处理一个事件的操作,可以由事件发生的事件源进行,而更多的可能是由其它对象来进行。比如,在一个窗口中的“退出”按钮被按下,该事件引起窗口的关闭,这个关闭的操作必须由窗口,而不是该按钮来进行。
事件机制中,操作事件发生后进程的主体并不会不断地查询事件是否发生,而应该等待事件源在事件发生时通知它该事件发生了。如窗口并不会不断地查询“退出”按钮是否按下,而应该在“退出”按钮按下事件发生时由该按钮通过某种方式来通知窗口事件发生了。
对于事件源而言,需要知道其事件发生的主体可能没有,可能有一个,也可能有多个。所以,当事件发生时,一般性地“说”:事件发生了,并给出事件发生的相关信息是事件源的责任,但哪些主体需要得到该通知则不是事件源的责任,而是其它主体的责任。即:需要监控某个事件源事件是否发生的主体必须负责监听事件源事件是否发生。
同一个事件源可以发生多种事件,同一类事件可以由不同的事件源引起。比如,按纽可以发生“按下”、“释放”、“获得焦点”等多个事件。而“获得焦点”事件也可以由按钮、窗体、列表框等多个事件源引起。
因此,事件处理机制必须表达和处理好这么几个东西:事件源,描述可能引起事件的对象的所有属性,包括在事件发生时发出通知。事件,描述和处理事件的所有信息。主体,负责对其关心的事件发生后的处理。事件监听,负责监听主体关心的事件源是否发生相关的事件,并在事件发生时将事件的信息传递给主体。
2.Java的事件处理机制概览
根据事件处理机制的总体情况,Java从总体上采用了四个对象(类)来完成对事件的处理。
事件源类:描述事件发生源的基本属性和行为。比如,按钮是很多事件发生的事件源,Java构造按钮类来描述按钮的形状,颜色等等属性以及在屏幕上绘制按钮等动作,当然也包括按钮按下等相关事件发生本身。同时,在事件发生时,事件源类还负责发出事件发生的通知。该行为通过事件源查找自己的事件监听者队列,并将事件信息通知队列中的监听者来文成。同时,事件源还在得到有关监听者信息时负责维护自己的监听者队列。
事件类:描述某个事件的所有属性和相关动作。比如定义键盘事件类KeyEvent来描述键盘事件的所有信息,如键是否按下、是否释放、是否双击,以及相关键的信息,如键码,是否有组合键等等。
事件监听者类,也就是关注事件的主体类:当事件发生后,该主体负责进行相关的事件处理,同时,它还负责通知相关的事件源,自己关注它的特定的事件,以便事件源在事件发生时能够通知该主体。
事件监听者接口:事件发生后,事件源要将相关的信息通知对应的监听者。这要求必须在事件源和监听者之间定义一个标准的接口规范,以便完成这样的信息交换。监听者接口正是负责定义这样的接口规范。
这四种类的实现有以下一些比较关键的地方。
事件源的监听者队列的维护。通常可以通过定义一个可以接受任何对象的队列来完成。因此,事件源应该至少做这么三件事:定义队列,通常通过定义一个Vector实例来接收和存储Object类型的监听者;定义添加监听者的方法,通常通过定义由监听者调用的addXXXListener方法完成向Vector队列中添加监听者;定义删除监听者的方法,通常通过定义由监听者调用的removeXXXListener方法来删除Vector队列中记录的监听者。
事件源事件的通知。在事件源发生某个事件后,事件源构造相应的事件类的实例,并在监听者队列中查找所有记录的监听者,并调用监听者对应的事件处理函数从而达到通知监听者的目的。这里,为了顺利地完成这种通知和调用,必须事先约定好这个处理函数接口。这种约定是由监听者接口来进行的。即在监听者接口中申明这些接口函数的名称和参数,而由监听者应用这些接口,并实现接口中的接口函数来完成。所以,接口函数实际上也就是监听者的对应事件的处理函数,在事件发生后监听者需要进行的操作都应该在这个函数中实现。同时,为了获得事件的信息,通常这样的接口函数都会传递对应的事件类参数。当然,也可以在接口中定义其它需要传递的参数。事件源构造对应的事件实例,并调用接口程序完成参数传递和事件通知。因此,在事件源中,需要编写相应的代码来完成事件队列的查询和接口函数的调用。
在Java中,由于应用接口必须在应用的类中实现接口中的所有申明函数,有时对于不需要使用所有申明函数的监听者比较麻烦,所以通常可以编写一个基础的适配器类来应用接口,适配器中预先实现所有的空接口函数,监听者则通过继承适配器并重载自己需要的接口函数来避免直接应用接口的麻烦。
在Java的类库中,提供了大多数常用的事件源、事件类、接口类及适配器类,因此实际使用中,只要在主体也就是监听者中应用接口或适配器,并实现相应的事件处理函数,然后利用addXXXListener添加相应的事件源的监听器,就完成事件机制的使用了。
当然,如果编制了自己的新事件源类,并且需要实现在Java类库中没有预先实现的事件,则还需要编制自己的事件类(通常派生自EventObject),编制相应的事件接口类(通常派生自EventListener)并预定义相应的接口函数,在事件源类中实现相应的addXXXListener方法和removeXXXListener方法,并完成对监听者队列的管理和事件通知代码的编写。
3.事件处理机制的联想
其实,在事件发生时,事件源通知监听者,其本质还是要在此时调用监听者的相应处理函数。在C和C++中,可以直接在事件源类中定义相应的处理函数指针,并在监听者类中将事件源类的指针指向自己的相应函数就完成了。当然,如果要实现多个监听者的监听,也可以在事件源中定义函数指针队列,并利用函数相应的函数来将相应监听者类的函数指针赋给队列元素,这样的处理方式就和前述方式相似的。
事件类的更多的目的是封装事件的相应信息,并方便在基础的事件类上的派生和重用。如果事件很简单,并且不存在继承等问题,其实可以不用构造事件类,直接简单地在事件源类中给出相关的事件信息就可以了。
同样,接口类也是为了申明监听者的事件处理函数的接口规范,并方面继承和代码重用。如果监听者固定且简单,也可以直接在监听者类中定义事件处理函数,而事件源直接调用就行了。
不过,专门定义事件类和接口类,能够使事件处理机制规范化,并且对于代码重用很有好处。在C和C++中,其实也可以按照类似的方式来考虑和组织事件处理机制。
4.事件处理机制的扩展应用
当我们定义我们自己的工具类库时,完全可以参照上述机制来组织我们自己的事件处理。这里,我们自己定义的类库应该是我们的事件源,而以后使用这个类库的应用程序则是监听者。在类库定义时,我们可以同时定义与该类库相关的事件类来封装事件的信息,也同时定义相应的接口类来申明监听者事件处理函数的接口规范,以及相应的适配器类来减少应用接口的麻烦。当然,为了提高代码的重用性,并且考虑到Java的相关语法规定,上述四个类必须都申明为public,所以必须分别写在各自与类名相同的文件中,但应该申明为同一个包。
下面,我们以常见的计算工具为例,说明如何自己建立自己的事件处理机制。
考虑我们可能会建立很多各种不同的计算工具,比如常规的优化计算工具(各种优化方法),遗传算法计算工具,微粒群算法计算工具等等。这些工具都是事件源,它们可能发生的事件包括:计算完成事件、计算放弃事件和单步计算完成事件(每计算完一步,但还没有完成所有计算就发生该事件)。为了能够方便这些计算工具的开发,我们可以组织一个所有这些计算工具的父类组织,包括抽象的计算类,计算事件类,计算事件接口类,计算事件适配器类来搭建所有这些工具的计算事件处理框架。一旦框架完成,具体的计算工具类使用时我们就和使用比如Java类库中的窗口、按钮等类一样,只需要应用程序中注册监听者,实现事件处理函数,然后进行计算就行了。具体的代码如下。
import java.util.*;
public class CalTst { //测试应用程序,即具体的监听者
public static void main(String[] arg) {
System.out.println("Test Begin:");
myCal
c1.addCalListener(new myCal1Adapter()); //注册工具1的监听者
c1.Run(); //运行工具1
myCal
c2.addCalListener(new myAdaper()); //注册工具2监听者
c2.Run(); //运行工具2
}
}
class CalEvent extends EventObject { //计算工具事件类
public static final int STEPOVER=1; //申明事件标志,单步计算完成
public static final int CALOVER=2; //计算完成标志
public static final int CALCANCEL=3; //计算取消标志
public static final int TYPEERROR=-1;
protected long StepNum; //单步计算的步数
protected int Type; //事件类型
public CalEvent(Object src) { //几个事件构造函数
super(src); //登记事件源
Type=TYPEERROR;
}
public CalEvent(Object src, int mask) {
super(src);
if(mask==STEPOVER || mask==CALOVER || mask==CALCANCEL) Type=mask;
else Type=TYPEERROR;
}
public CalEvent(Object src, int mask, long step) {
super(src);
if(mask==STEPOVER || mask==CALOVER || mask==CALCANCEL) Type=mask;
else Type=TYPEERROR;
StepNum=step;
}
//一些实用方法
public void setType(int mask) { Type=mask; }
public void setType(int mask, long step){Type=mask; StepNum=step;}
public int getType() { return Type; }
public long getStepNum() { return StepNum; }
public boolean isStepCal() {
if(Type==CalEvent.STEPOVER) return true;
else return false;
}
public boolean isCalOver() {
if(Type==CalEvent.CALOVER) return true;
else return false;
}
public boolean isCalCancel() {
if(Type==CalEvent.CALCANCEL) return true;
else return false;
}
}
interface CalListener extends java.util.EventListener { //事件接口类
public boolean StepProformed(CalEvent event); //申明单步运算函数接口
public void CalOverProformed(CalEvent event); //申明计算完成函数接口
public void CancelProformed(CalEvent event); //申明计算取消函数接口
}
class CalAdapter implements CalListener { //事件适配器类,实现3个空处理函数
public boolean StepProformed(CalEvent event){return true;}
public void CalOverProformed(CalEvent event){}
public void CancelProformed(CalEvent event){}
}
class Calculation { //计算类,实际的计算事件源
protected Vector listener; //监听者队列
public Calculation(){ listener=new Vector(); }
public synchronized void addCalListener(CalListener l) { listener.add(l); }//添加监听者
public synchronized void removeCalListener(CalListener l){listener.remove(l);}
public boolean notifyEvent(int mask) { //事件通知函数重载版本1
Vector l=new Vector();
l=(Vector)listener.clone();
boolean rt=true;
CalEvent ev=new CalEvent(this);
if(mask==CalEvent.STEPOVER) ev.setType(CalEvent.STEPOVER,0);
else ev.setType(mask);
synchronized(this) { //多线程锁定
for(int i=0; i<l.size(); i++) { //查询监听者,并调用处理函数
if(ev.isStepCal()) rt=((CalListener)l.elementAt(i)).StepProformed(ev);
else if(ev.isCalOver()) ((CalListener)l.elementAt(i)).CalOverProformed(ev);
else if(ev.isCalCancel()) ((CalListener)l.elementAt(i)).CancelProformed(ev);
}
}
return rt;
}
public boolean notifyEvent(int mask, long step) { //事件通知函数重载版本2
Vector l=new Vector();
l=(Vector)listener.clone();
boolean rt=true;
CalEvent ev=new CalEvent(this);
if(mask==CalEvent.STEPOVER) ev.setType(CalEvent.STEPOVER,step);
else ev.setType(mask);
synchronized(this) {
for(int i=0; i<l.size(); i++) {
if(ev.isStepCal()) rt=((CalListener)l.elementAt(i)).StepProformed(ev);
else if(ev.isCalOver()) ((CalListener)l.elementAt(i)).CalOverProformed(ev);
else if(ev.isCalCancel()) ((CalListener)l.elementAt(i)).CancelProformed(ev);
}
}
return rt;
}
}
class myCal1 extends Calculation { //计算工具1类
public void Run() { //运行工具1方法
for(int i=0; i<5; i++) notifyEvent(CalEvent.STEPOVER,i);//产生单步事件
notifyEvent(CalEvent.CALOVER); //产生计算完成事件
}
public String getResult(){ return "OK"; } //返回计算完成结果
}
class myCal1Adapter extends CalAdapter { //计算工具1适配器
public boolean StepProformed(CalEvent e) { //单步事件处理函数
System.out.println("Step "+e.getStepNum());
return true;
}
public void CalOverProformed(CalEvent e) { //计算完成处理函数
System.out.println("The Result is "+((myCal1)e.getSource()).getResult());
}
}
class myCal2 extends Calculation { //计算工具2类
public void Run() { //计算工具2运行
notifyEvent(CalEvent.CALOVER); //产生计算完成事件
}
}
class myAdaper extends CalAdapter { //计算工具2适配器
public void CalOverProformed(CalEvent e) {//计算完成处理函数
System.out.println("The Source is "+e.getSource().toString());
}
}
运行上述例程,可以获得如下结果:
Test Begin:
Step 0
Step 1
Step 2
Step 3
Step 4
The Result is OK
The Source is myCal2@7259da
可以看出,应用程序利用注册的监听器及其事件处理函数,完全获得了计算事件对应的运行结果。这种事件处理框架完全可以应用于我们自己的计算类工具库中。
联系客服