您好,欢迎访问三七文档
3.4事件驱动——有事我叫你,没事别烦我关键词:编程范式,事件驱动式,回调函数,framework,IoC,DIP,观察者模式摘要:事件驱动式编程简谈?提问什么是事件?有哪些不同类型的事件?什么是回调函数?什么是异步同调?它们有什么用处?控制反转的目的是什么?它是如何实现的?在框架设计中起什么作用?控制反转、依赖反转原则和依赖注射的共同点是什么?事件驱动式编程有哪些关键步骤?异步过程特点和作用是什么?事件驱动式编程最重要的特征是什么?它们是如何实现的?事件驱动式与观察者模式、MVC模型有何关系?:讲解逗号渐觉睡虫上脑,开始闭目点头。正神游之际,忽觉腰间一阵酥麻。惺眼微睁,原是被引号的胳膊肘给捅的,顿时警醒。抬头见讲台上的老冒正目光灼灼地盯着自己,不禁脸颊微烫,嗫嚅道:“不好意思,昨晚睡得太晚了。”冒号却不以为意:“正愁找不到新话题呢,你倒启发我了。话说课堂上睡觉大抵有三种方式——”话音未落,有人已笑不自禁。“第一种是警觉式:想睡可又担心被老师发现,不时睁眼查看周围的变化。同时双耳保持警戒,一有异动立刻挺直身板。”冒号有板有眼地形容,“第二种是宽心式:俯桌酣睡,如处无人之境。境界至高者或可雷打不动,或可鼾声如雷。”“总之是很雷人。”叹号的网络新语再度引发笑声。冒号继续分析:“第三种是托付式:请人放哨,非急勿扰。遂再无顾忌,大可封目垂耳,安心入眠。请问你们乐意采用哪种方式?”“第一种方式睡不踏实,不得已而为之。敢用第二种方式的人多半没心没肺,估计IT人都达不到那种境界。只要有同伴在身旁,我想大家都会选第三种方式的。”句号的回答获得一致认同。冒号续问:“好,抛开第二种方式不谈,为什么第三种要比第一种优越呢?”句号回答:“犯困者既要打盹又要警戒,必然苦不堪言。如果把警戒的任务委托同伴,两人分工合作,自然愉快得多。”冒号再问:“他们是如何合作的呢?”“放哨者一旦发现有情况,立即通知犯困者采取行动——睁眼坐直,作认真听讲状。”句号说得是绘声绘色。除了两位当事人略显尴尬外,其他人均乐不可支。眼见时机成熟,冒号不再兜圈:“采用警觉式者主动去轮询(polling),行为取决于自身的观察判断,是流程驱动的,符合常规的流程驱动式编程(Flow-DrivenProgramming)的模式。采用托付式者被动等通知(notification),行为取决于外来的突发事件,是事件驱动的,符合事件驱动式编程(Event-DrivenProgramming,简称EDP)的模式。下面我们就来说说这种编程范式。”逗号瓮声瓮气道:“没想到打瞌睡打出了个范式。”冒号瞥了他一眼,继续说下去:“为完成一样事,既可以采用流程驱动式,也可以采用事件驱动式。这样的例子在生活中可谓俯拾即是,刚才逗号同学为大家现场示范了一个,谁还能举出其他范例?”叹号抢先举例:“与客户打交道,推销员主动打电话或登门拜访,他的工作是流程驱动的;接线员坐等电话,他的工作是事件驱动的。”问号也说:“同样是交通工具,公共汽车主要是流程驱动的,它的路线已预先设定;出租车主要是事件驱动的,它的路线基本上由随机搭载的乘客所决定。”引号以个人经验作例:“购买喜爱的杂志可以选择频繁光顾报刊亭,也可以选择一次性订阅。浏览关注的新闻网站或博客,可以直接访问站点,也可以订阅相应的RSS。主动检查所关心的内容是否更新是流程驱动的,用订阅的方式是事件驱动的。”句号回到本行:“Windows下的许多工作既可以在DOS下用批处理程序实现,也可以在图形界面下完成。前者不需人工干预,显然是流程驱动的;后者毫无疑问是事件驱动的。”“看来你们对这种范式很熟悉嘛。不过,它原理虽简单,威力却无穷。看似一招,实则暗藏百式,甚可幻化千招。个中精妙之处,断非一时可以尽述。”冒号不知不觉中又走进了武侠的世界。众人听了,暗疑老冒有些言过其实。冒号正式入题:“首当其冲的问题是:何谓事件?通俗地说,它是已经发生的某种令人关注的事情。在软件中,它一般表现为一个程序的某些信息状态上的变化。基于事件驱动的系统一般提供两类的内建事件(built-inevent):一类是底层事件(low-levelevent)或称原生事件(nativeevent),在用户图形界面(GUI)系统中这类事件直接由鼠标、键盘等硬件设备触发;一类是语义事件(semanticevent),一般代表用户的行为逻辑,是若干底层事件的组合。比如鼠标拖放(drag-and-drop)多表示移动被拖放的对象,由鼠标按下、鼠标移动和鼠标释放三个底层事件组成。”问号推想:“编程人员应该还能创造新的事件类型吧?”“那是当然。”冒号点点头,“还有一类用户自定义事件(user-definedevent)。它们可以是在原有的内建事件的基础上进行的包装,也可以是纯粹的虚拟事件(virtualevent)。除此之外,编程者不但能定义事件,还能产生事件。虽然大部分事件是由外界激发的自然事件(naturalevent),但有时程序员需要主动激发一些事件,比如模拟用户鼠标点击或键盘输入等,这类事件被称为合成事件(syntheticevent)[1]。这些都进一步丰富完善了事件体系和事件机制,使得事件驱动式编程更具渗透性。”叹号嘟哝了一句:“看来这里边还有点名堂。”“名堂多着呢!”冒号回应,“事件固然是事件驱动式编程的核心概念,但一个编程范式的独特之处绝不仅仅是一些概念,更重要的是建立于这些概念之上的思维模式。为了了解这种范式与众不同的特点,我们先看看如何利用win32的API在windows下创建一个简单的窗口——”/**一个win32窗口程序*/…WinMain(...)//windows应用程序的主函数{//第一步——注册窗口类别...;windowClass.lpfnWndProc=WndProc;//指定该类窗口的回调函数windowClass.lpszClassName=windowClassName;//指定该类窗口的名字RegisterClassEx(&windowClass);//第二步——创建一个上述类别的窗口CreateWindowEx(…,windowClassName,...);…;//第三步——消息循环while(GetMessage(&msg,NULL,0,0)0)//获取消息{TranslateMessage(&msg);//翻译键盘消息DispatchMessage(&msg);//分派消息}}//第四步——窗口过程(处理消息)…WndProc(…,msg,...){switch(msg){caseWM_SIZE:…;//用户改变窗口尺寸caseWM_MOVE:…;//用户移动窗口caseWM_CLOSE:…;//用户关闭窗口…;}}“没有选用Java、VisualC++、C#、VB或者Delphi来实现窗口,是因为它们高度的封装和强大的IDE掩盖了部分事件机制。如果你们对win32API不太熟悉,没有关系。为了减少语言和API上的障碍,同时突出重点,这里最大限度地省略了次要的过程和参数等,仅保留脉络主干。”冒号解释,“从中看出到,创建一个能响应用户操作的win32窗口共分四步:注册窗口类别、创建窗口、消息循环和窗口过程。”问号对概念很敏感:“消息与事件是一回事吗?”“严格说来它们不是一回事,但如果你不想深究,不加区分也无大碍。概略地说,消息是Windows内部最基本的通讯方式,事件需要通过消息来传递,是消息的主要来源。每当用户触发一个事件,如移动鼠标或敲击键盘,系统都会将其转化为消息并放入相应程序的消息队列(messagequeue)中[2]。”冒号解答着,“明白了这一点,上面的代码就不难理解了——在消息循环中,程序通过GetMessage不断地从消息队列中获取消息,经过TranslateMessage预处理后再通过DispatchMessage将消息送交窗口过程WndProc处理。”逗号琢磨了一会,不解地问:“窗口过程应该是在分派消息时被调用的,但我怎么想不出DispatchMessage是如何联系到WndProc的?”冒号为其解惑:“DispatchMessage的消息参数含有事发窗口的句柄(handle),从而可以得到窗口过程WndProc[3]。至于窗口与窗口过程之间是如何建立联系的,回看前面两步就一目了然了:当初在创建窗口时指明了窗口类别名windowClassName,而窗口类别windowClass又绑定了窗口过程。”叹号有点纳闷:“干嘛要绕这么大的弯子,直接调用WndProc不就得了?”“对于这个简单的程序来说,的确区别不大。但假如再增添其他菜单、按钮、文本框之类的控件,每个控件都可绑定自己的窗口过程,那么到底该调用哪个才对呢?”冒号反问。叹号虽有所悟,但仍有心结:“总觉得窗口过程的用法有些怪怪的。”冒号一敲桌案:“没错!怪就怪在编程者自己写了一个应用层的函数,却不直接调用它,而是通过库函数间接调用。这类函数有个专用名称:回调函数(callback)。”引号忍不住插话:“回调函数我知道,在C和C++中就是函数指针嘛。”“确切地说,函数指针是C和C++用来实现callback的一种方式。此外,C++中的functor、Java中的interface和C#中的delegate都可实现callback。我们先图解一下回调机制。”冒号调出一张图示——“如果我们把系统划分为两层[4]:底层的函数库和高层的应用程序。同样作为主函数的辅助函数,左图中的普通函数直接被主函数调用,然而右图中的回调函数却是通过库函数间接被主函数调用的。”冒号的手影在幻灯下上下翻飞。句号点出要害:“一般都是高层代码调用低层代码,callback反其道而行之,因此显得与众不同。”“所言极是。一方面,在软件模块分层中,低层模块为高层模块提供服务,但不能依赖高层模块,以保证其可重用性和可扩展性;另一方面,通常被调者(callee)为调用者(caller)提供服务,调用者依赖被调者。两相结合,决定了低层模块多为被调者,高层模块多为调用者。callback的出现改变了这种惯例,我们看一个简单的例子。”冒号写下一段Java代码——String[]strings={Please,sort,the,strings,in,REVERSE,order};Arrays.sort(strings,newComparatorString(){publicintcompare(Stringa,Stringb){return-a.compareToIgnoreCase(b);}});引号很快读懂了代码:“这是将字符串组不区分大小写地逆序排列。其中Comparator的匿名类实现了callback,因为它的方法compare是在类库中被调用的。”“此处callback的好处是显而易见的——它使得Arrays.sort不再局限于自然排序,允许用户自行定制排序规则,大大提高了算法的重用性。”冒号说着将幻灯片又翻到前页,“回头再看win32窗口程序的例子,其中第三步消息循环那段代码不依赖应用程序代码,完全可以提炼出来作为library的一部分。事实上,在VisualC++里这段代码就‘下放’到MFC类库中去了。假设窗口过程由应用程序直接调用,那么消息循环中的代码将不再具有独立性,无法作为公因子分解出来。”叹号块垒顿消,畅然无比:“终于搞清那个怪异的窗口过程了!每个窗口在创建时就携带了一个callback,以后每当系统侦查到事件,都能轻易地从事发窗口身上找到它的callback,然后调用它以响应事件。”“这等于将侦查事件与响应事件两项任务进行了正交分解,降低了软件的耦合度和复杂度。”句号言犹未尽,又加了一句,“就像刚才,引号负责侦查事件——警戒,逗号负责响应事件——警醒。想法很好,可惜配合不够默契,还是给人逮住了。”逗、引二人大窘,余者大笑。“仔细比较,以上两个callback的用法还是稍有不同的。在字符串组排序中,callback在作为参数传入底层的函数后,很快就在该函数体中被调用;在窗口程序中,callba
本文标题:事件驱动入门
链接地址:https://www.777doc.com/doc-2735820 .html