事件驱动编程(未完成,写作中...) 作者:Achilles Xu 事件驱动(event-driven)编程主要应用于桌面程序开发、web客户端javascript开发、flash开发、服务器端守护进程开发等各个领域。它可以被看成是一套架构、一套机制、一种设计模式甚至是一种理念。如果你深入理解它,你也可以把它用于web服务器端脚本的开发,来处理单个页面请求的流程分支或者实现极端复杂的多表单、多页面工作流。 事件驱动编程由驱动机制(event loop和event queue)、广播者(broadcaster)和订阅者(subscriber)三部分组成。我们可以想象一个窗口或者网页上的一个按钮。当它被按下的时候就产生的一个事件(onclick)。这时,这个按钮就是广播者。onclick事件及其所带的参数被push到event queue(消息队列)里。event loop不断地循环,按照一定的顺序(进队时间、优先级等)从event queue里取出事件来进行处理。event loop每取出一个事件,就查找它的所有订阅者,并通知它们。订阅工作必须在这之前完成。 <script language="javascript"> function send_button_click() { window.alert("button clicked!"); } <input type="button" value="发送" onclick="send_button_click" /> 在网页加载的时候,onclick事件就已经被send_button_click()函数订阅了。当onclick事件被event loop处理时,就会执行send_button_click()函数(该例会弹出一个提示框告知用户:按钮被按下)。 初级程序员一般都停留在编写订阅者的层次上,也就是说只是响应事件,而不是产生和广播事件,更不用说自己事件消息驱动了。我们说事件驱动可以是一种设计模式。让我们来看看初级程序员的代码。假设写一个桌面程序,当点击发送按钮的时候,需要通过ftp传送一个文件到远程服务器上。下面是一般程序员的伪代码: void send_button_onclick() { FTPClient ftp = new FTPClient(); ftp.connect(服务器地址, 端口); if (ftp.连接成功()) { ftp.登录(用户名, 密码); if (ftp.登录成功) { ftp.传送文件(本地文件名, 目标路径); if (ftp.传送成功) { 提示用户:传送成功; } else { switch(ftp.失败原因编号) { case 1: 提示用户找不到本地文件; break; case 2: 提示用户连接异常断开; break; case 3: 提示用户远程目录没有写权限; break; } } } else { 提示用户密码错误; } } else { 提示无法连接; } } 我们看到这种写法只响应了一个事件:onclick,然后就在这个事件处理程序里用最古老的面向过程的方法一口气完成所有工作。就是:你要是提供事件,我就响应你,没有的话,我就还写我的面向过程。这样一般会产生大量if嵌套,结构混乱,容易产生逻辑错误。有些程序员会使用封装,用类或者函数来拆分和重用代码,结果是产生很深的函数堆栈。再来看看用事件驱动方法编写的代码: FTPClient ftp = new FTPClient(); // 注册事件 addListener("连接成功", connent_ok); addListener("登录成功", login_ok); addListener("传送失败", send_fail); // 开始发送 void send_button_onclick() { ftp.connect(服务器地址, 端口); if (ftp.连接成功()) { postEvent("连接成功"); } else { 提示无法连接; } } void connect_ok() { ftp.登录(用户名, 密码); if (ftp.登录成功) { postEvent("登录成功"); } else { 提示用户密码错误; } } void login_ok() { ftp.传送文件(本地文件名, 目标路径); if (ftp.传送成功) { 提示用户:传送成功; } else { postEvent("传送失败", "失败原因"); } } void send_fail(失败原因) { switch(ftp.失败原因编号) { case 1: 提示用户找不到本地文件; break; case 2: 提示用户连接异常断开; break; case 3: 提示用户远程目录没有写权限; break; } } 这里我们把整个过程拆分成了几个小步骤:连接、登录、发送。这些步骤可能产生的结果:连接成功、连接失败、登录成功、登录失败、发送成功、发送失败。对于这些结果,其中连接成功、登录成功和发送失败的后续处理比较复杂。我们把他们生成广播事件,放到event queue里。在程序初始化的时候我们自己就订阅了这些事件。这种自己广播自己订阅的模式,也叫post_back,把复杂的处理切分、延后,一步一步来。相反的模式叫call_back,多数情况是他人订阅,就是一发生就处理,实际上造成了函数堆栈的加深。设计模式中的listener模式就是这种情形:因为没有event-loop和event-queue,所以无法实现延后。上面这段用事件驱动方法写的代码是不是比用面向过程方法写的代码清晰多了? 能常想着广播事件,代码结构就能清晰多了,而且在很多面向对象封装当中用事件来传递信息、互相协作很方便。这样基本可以算是中级程序员水平了。 |
|
来自: cherishchen > 《我的图书馆》