我对事件驱动的理解

我对事件驱动的理解
作者:axgle

引子

“事件驱动”这四个字,我是在学习javascript过程中遇到的,例如"onclick事件".
后来学习visual Basic,也遇到了这四个字----“事件驱动”。
再后来了解.net以及学习flash脚本语言的过程中,也遇到过...
甚至在PHP的一个blog程序,名叫wordpress的插件机制中,也见到了“事件驱动”的影子.
终于,在一个下雨的傍晚,我坐在窗前,双手托住下巴,开始思索“事件驱动到底是什么,为什么要使用事件驱动,以及如何建立事件驱动机制”的问题。


一.事件驱动到底是什么意思?

无论是javascript,还是visual Basic,无论是.net系列,还是java语言。。。无论是面向过程也好,面向对象也罢,其共有
的一个语法结构就是“IF语句”,也叫“条件语句”。其最基本的形式可以表示为“if A then B”.

我把“if A then B”语句中的A称呼为‘条件’,而把B称呼为“结果”,希望你不要反对:)
“如果你反对,那么我誓死捍卫你说话的权利”----你看,条件语句是多么容易出现的事情啊~

我把“if A then B”语句中的A称呼为‘事件’,而把B称呼为“响应”,希望你也不要反对:)
“如果你反对,那么我将煽你一耳光”----其中,‘你反对’是一个事件,而“我将煽你一耳光”是我对该事件作出的“响应”.

你看,axgle真是个‘莫名其妙,变化多端’的人,他一会儿'誓死捍卫你说话的权利',一会儿又要"煽你一耳光",真搞不懂他的变化为什么如此的快。
虽然这里的axgle比较“变态”,但这里也包含了“不变”的成分:一.事件"你反对"常常容易遇到,可以认为是“不变的”;二.if-then的“逻辑关系”是确定的,
也就是说,"if"与"then"两者的关系是确定的。简单的说,这里的“事件”和“逻辑关系”是"不变"的。

让我们用上面的观点来分析"onclick事件",也就是“鼠标单击”事件:

“如果用户用鼠标单击,那么就显示菜单的详细列表”。你看,这里的“鼠标单击”在“如果”里,而后面的“显示菜单的详细列表”是对这个事件的
“响应”。
“如果鼠标单击关闭按钮,那么就关机”。你看,这里又有一个“鼠标单击”事件,是不是?可见,“哪里有'如果',哪里就有'事件'”
“如果鼠标单击关闭按钮,那么就弹出一个新窗口并且给个提示”,你看,同样的“事件”,“响应”却可以有所不同(因为不同的程序员,不同的设计,不同的需求等等)。

但无论如何,鼠标单击总是常常遇到的,而鼠标单击与其后的响应之间的“逻辑关系”也是确定的。
“如果用户进行无数次鼠标单击,那么依然不出现任何反应”。妈的,什么破玩意,靠,是不是死机了??
可见,就算是“死机”,那也是一种“响应”,只不过是让人讨厌的“响应”而已。但无论如何,这里的"if-then"的逻辑结构是不变的。
也许,黑客就比较喜欢这一点,因为他想把你的机器搞死。哈哈

可见,事件驱动就是“在保持事件及其逻辑关系不变的情况下,根据需要定义事件的响应的机制”。
其中,'事件'可以用event来表示,'事件的响应'可以用handler来表示,而这里的“逻辑关系”
是指event和handler两者存在的"If event Then handler"的逻辑关系.

二.为什么要使用事件驱动?

事件驱动的特点在于“不变与万变”的有机结合。对于给定的“If event Then handler”,实际上包含三个元素:
1.事件(event);2.响应(handler);3.事件与响应的“逻辑关系”
其中的'事件'常常是固定的;而“逻辑关系”往往体现的是业务规则或者核心逻辑,也常常是固定不变的。
只有'响应'是可变的,或者说是可以配置或者需要改变的。

软件开发的OCP原则告诉我们:“软件应该对修改关闭,但要对扩展开放”。而事件驱动就是在保持了事件和核心逻辑的稳定性和不被修改
的前提下,通过定义不同的“响应”从而达到了对“扩展的开放”。

因此,事件驱动机制是一种可重用和可扩展性比较强的机制。

以上就是理论上的证明。下面有必要进一步“普遍化”:在结构控制方面,顺序,选择(即条件),循环三种结构,理论上都可以用选择结构来理解。
例如顺序结构,“语句A;语句B;语句C”,按照人类的理解,可以用"如果A出现后,就出现B;如果B出现后,就出现C".
又如循环结构,依然可以用if-then的语句来描述,这里从略。

换句话说,在人类看来,任何程序语句,都可以看作是条件语句。因此,‘事件’在程序中便无处不在,无时不有。这就是“事件驱动的普适性”。

因为“事件驱动的普适性”,所以任何程序语言在理论上都可以建立“事件驱动的机制”。

事件驱动的机制由于把“响应”作为应付变化的要素,同时保持了“事件和逻辑关系”的稳定性,从而为程序的维护与扩展打下了良好的基础。

三.如何建立事件驱动的机制?

在面向对象或者基于对象的程序语言中,常常已经建立了明确的事件驱动的机制,例如.net系列或者javascript等等。那么这些语言的内部到底是如何运作的呢?
我们可以用C语言或者PHP这些没有明显的“事件驱动机制”的语言说起。

“回调函数(callback)”可以说是面向过程语言建立‘事件驱动机制’的基础;同样,回调(CALLBACK)的概念也可以解释面向对象语言的"事件驱动"的内部过程。
你可以在网上搜索"回调函数",看看它是什么意思。下面简单的给一个php的例子.

我们知道,php处理错误,调试程序有一个函数,叫做set_error_handler,其原型如下:
set_error_handler ( callback_error_handler [, int error_types] )
其中重要的是第一个参数,callback_error_handler,它表示“当php出错的时候,将要调用的自定义的函数名”。
这样,我们自定义一个函数test_callback(),然后把这个函数的名字test_callback传递给set_error_handler,那么当php出错的时候,test_callback就会执行。

这里可以看出其中隐含的‘事件驱动机制’:‘出错’是一个‘事件’,而test_callback()是一种'响应'。通过使用set_error_handler,建立了两者的逻辑关系。
而这里set_error_handler,本质上就是使用了所谓的"回调函数"的概念。

执行php中的回调函数一般使用的是"call_user_func_array或者call_user_method_array"等等,具体你可以查阅手册,看看它们的作用。其他语言例如c语言也有
回调函数的概念,具体情况请查阅相关资料。

因为事件驱动的机制把“响应”作为应付变化的要素,同时保持“事件和逻辑关系”的稳定性,所以'响应'常常是自定义的。
一个响应常常表现为一个函数,在一段程序中,当事件出现后,就用回调函数的方式调用响应函数,这样,我们只需要修改响应函数,而事件和程序的逻辑关系却可以
保持稳定,同时把响应独立了出来,作为了可变化和修改的独立部分。

考虑如下情形:
test_callback();语句1;语句2;语句3;...call_user_func('test_callback');...语句N
注意,其中的语句1到语句N是核心逻辑,所以这些语句的‘位置’是不能修改的,而test_callback部分则是可以自定义的。那么为什么要使用call_user_func('test_callback')
而不是直接执行test_callback()呢?

假如直接执行test_callback(),那么test_callback函数必须是“已定义的函数”。正是因为要在特定的位置执行“未定义的响应函数”,所以才需要使用“回调函数”的方式。

把事件后面的响应函数提到前面定义,当事件发生后,就执行回调,也就是执行响应函数。这就是建立事件驱动机制的一般方法。

说到这里,也许你还是不明白为什么非要使用回调函数。你可以设想如下情形:

function call_user_func(function_name){
if( function_name函数存在 ) 则执行function_name
}

上面定义的call_user_func回调函数的方式,可以预先检查响应函数是否定义或者是否存在,然后才考虑是否执行以及如何执行,这样就不会出现“函数没有定义”的语法错误。
并且,使用回调函数的方式,你还可以建立“增加新的回调,删除已有的回调,修改当前的回调,查询所有的回调函数”等等“回调操作”的功能。

有兴趣的朋友,可以研究一下php中wordpress程序里的插件机制,主要的几个函数为"add_action,do_action,add_filter...等等".这几个函数,就是wordpress建立
的回调操作函数,其在整个wordpress中的具体应用,体现的就是这里的事件驱动机制。


以上就是面向过程语言(例如php)建立事件驱动机制的办法。而在面向对象的语言中,依然靠的是回调的方式,只不过传递的不是‘回调函数名’,而是传递的类名,而类中其实
已经绑定了方法(本质上依然是函数)。因此可以说面向对象的语言依然使用的是回调函数的方式建立的事件驱动的机制。


结语:

事件驱动依照“事件-响应”的模式,通过回调的方式执行自定义的响应函数,保持了事件和程序逻辑的稳定性和扩展的开放性,从而为程序的开发和维护提供了有力的帮助。
[该贴被admin于2009-03-19 10:36修改过]