今天我们来谈一谈Java里面的Event事件机制。
程序的结构在不同的时代是在变化的。刚学编程序的时候,老师总会讲,程序是一个流程,线性执行,分支跳转,循环,有时候加上递归。我们总是能一步步跟踪下来,知道程序确切的运行次序。后来就会碰到事件的概念,程序先是准备好了,然后等待你做出某种反应,输入,键,鼠标,程序获取事件的消息,执行相关的动作。
这种程序结构之下,不再有事先确定的运行次序了。
刚开头遇到这种程序的时候,还真有点不习惯。印象最深刻的就是将近三十年前刚学到Windows编程的时候,看的第一个程序简直把人弄懵了。
我把程序片段贴下来,你们当欣赏历史文物看看:
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){
RegisterClass (&wndclass)
hwnd = CreateWindow( szAppName, // window class name
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){
switch (message) {
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello, World!"), -1, &rect,DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
你看上面的C程序代码,刚开头就是创建了一个窗口,然后一个死循环等待消息while (GetMessage (&msg, NULL, 0, 0)),消息来了就分派出去,交给相应的程序来处理。
这个处理的代码就是WndProc(),里面根据不同的消息类型执行不同的动作(第一次瞪大了眼睛打着灯笼才终于看到了“Hello World!”)。
Windows上写一个Hello World就这么麻烦!
但是它生命力顽强,最后成长为主流的模式。
我想这种基于事件驱动的方式是一个主要的优势。程序像一个服务机构,客户有什么需求,发来消息,服务机构去处理。这种程序结构很贴近人的行为。
我们可以进一步联想到面向对象编程的范式,它认为一个系统是由一个个对象构成的,每个对象有自己的功能和数据,独立成单元,发生什么事件后,通过事件消息传递给别的对象进行通知处理。
这个范式最后成为主流,现在几乎所有的系统都是按照这个范式构建的。
这种方式为什么好呢?理论上的研究表明,需求构成了“问题空间”,程序构成了“解决空间”,两个空间的相似度决定了解决的困难度。
传统上,我们要解决一个问题,程序给我们提供的却是寄存器、内存、加法这一类机器的概念术语,所以那个时候将问题空间映射到解决空间的时候很复杂。而面向对象事件驱动的模型,跟现实世界的问题空间有一定程度的相似度,我们能够比较省力地进行映射,解决问题。这个现实世界,本就是一个一个主体客体构成,它们发生了什么事情,本来就是通过消息传递方式联系在一起的。
聊了这些理论上的东西,我们回过头来看Java是怎么弄的。
在Java的事件概念中,有三个东西要了解,一个是事件源,一个是事件消息,一个是事件监听者。这三个词字如其意,不解释。流程是事件源发生了某事件的时候,发出事件消息,由事件监听者接受处理。事件从事件源到监听者的传送是通过对监听者的方法调用进行的(这一点有点意外,刚开头还以为是某种“消息传递”机制。
这个机制是存在的,在探讨更大规模的软件结构的时候,会介绍到。我们现在讨论的是一种代码结构,还不是软件结构,更加不是系统结构。我这里用代码结构、软件结构、系统结构三个词表达不同层次的程序规模。)
我们先来看如何定义事件。在java.util中提供了EventObject,所有的事件都要继承它。
我们自己来定义一个,代码如下(MyEvent.java):
package com.test;
import java.util.EventObject;
public class MyEvent extends EventObject {
private int state;
private String msg;
public MyEvent(Object source) {
super(source);
state = ((Source)source).getState();
msg = ((Source)source).getMessage();
}
public int getSourceState() {
return this.state;
}
public String getSourceMessage() {
return this.msg;
}
}
上面定义了一个由Source触发出来的事件MyEvent。构造函数可以有多个,我这里只用了一个默认的,传递的值是source。event中简单记录了状态和一条消息。
我们再看怎么定义事件监听者,代码如下(StateChangeListener.java):
package com.test;
import java.util.EventListener;
public class StateChangeListener implements EventListener {
public void handleEvent(MyEvent event) {
System.out.println(event.toString() + " fire event " + event.getSourceMessage());
}
}
按照要求,所有事件监听者要实现java.util.EventListener接口,这个接口是一个空的,只是一个标记(Tagging),习惯上会加一个handleEvent()方法,这个方法利用传递过来的事件消息进行相应处理。
好,