<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    冒號(hào)課堂§3.4:事件驅(qū)動(dòng)

     

    冒號(hào)課堂

    第三課 常用范式(4)

     

    3.4事件驅(qū)動(dòng)——有事我叫你,沒事別煩我

    勞心者治人,勞力者治于人                                           ——《孟子·滕文公上》

     

    關(guān)鍵詞:編程范式,事件驅(qū)動(dòng)式,回調(diào)函數(shù),framework,IoC,DIP,觀察者模式

    摘要:事件驅(qū)動(dòng)式編程簡(jiǎn)談

     
    提問

    • 什么是事件?有哪些不同類型的事件?
    • 什么是回調(diào)函數(shù)?什么是異步同調(diào)?它們有什么用處?
    • 控制反轉(zhuǎn)的目的是什么?它是如何實(shí)現(xiàn)的?在框架設(shè)計(jì)中起什么作用?
    • 控制反轉(zhuǎn)、依賴反轉(zhuǎn)原則和依賴注射的共同點(diǎn)是什么?
    • 事件驅(qū)動(dòng)式編程有哪些關(guān)鍵步驟?
    • 異步過程特點(diǎn)和作用是什么?
    • 事件驅(qū)動(dòng)式編程最重要的特征是什么?它們是如何實(shí)現(xiàn)的?
    • 事件驅(qū)動(dòng)式與觀察者模式、MVC模型有何關(guān)系?

      講解

     逗號(hào)漸覺睡蟲上腦,開始閉目點(diǎn)頭。正神游之際,忽覺腰間一陣酥麻。惺眼微睜,原是被引號(hào)的胳膊肘給捅的,頓時(shí)警醒。抬頭見講臺(tái)上的老冒正目光灼灼地盯著自己,不禁臉頰微燙,囁嚅道:“不好意思,昨晚睡得太晚了。”

    冒號(hào)卻不以為意:“正愁找不到新話題呢,你倒啟發(fā)我了。話說課堂上睡覺大抵有三種方式——”

    話音未落,有人已笑不自禁。

    “第一種是警覺式:想睡可又擔(dān)心被老師發(fā)現(xiàn),不時(shí)睜眼查看周圍的變化。同時(shí)雙耳保持警戒,一有異動(dòng)立刻挺直身板。”冒號(hào)有板有眼地形容,“第二種是寬心式:俯桌酣睡,如處無人之境。境界至高者或可雷打不動(dòng),或可鼾聲如雷。”

    “總之是很雷人。”嘆號(hào)的網(wǎng)絡(luò)新語再度引發(fā)笑聲。

    冒號(hào)繼續(xù)分析:“第三種是托付式:請(qǐng)人放哨,非急勿擾。遂再無顧忌,大可封目垂耳,安心入眠。請(qǐng)問你們樂意采用哪種方式?”

    “第一種方式睡不踏實(shí),不得已而為之。敢用第二種方式的人多半沒心沒肺,估計(jì)IT人都達(dá)不到那種境界。只要有同伴在身旁,我想大家都會(huì)選第三種方式的。”句號(hào)的回答獲得一致認(rèn)同。

    冒號(hào)續(xù)問:“好,拋開第二種方式不談,為什么第三種要比第一種優(yōu)越呢?”

    句號(hào)回答:“犯困者既要打盹又要警戒,必然苦不堪言。如果把警戒的任務(wù)委托同伴,兩人分工合作,自然愉快得多。”

    冒號(hào)再問:“他們是如何合作的呢?”

    “放哨者一旦發(fā)現(xiàn)有情況,立即通知犯困者采取行動(dòng)——睜眼坐直,作認(rèn)真聽講狀。”句號(hào)說得是繪聲繪色。

    除了兩位當(dāng)事人略顯尷尬外,其他人均樂不可支。

    眼見時(shí)機(jī)成熟,冒號(hào)不再兜圈:“采用警覺式者主動(dòng)去輪詢polling),行為取決于自身的觀察判斷,是流程驅(qū)動(dòng)的,符合常規(guī)的流程驅(qū)動(dòng)式編程Flow-Driven Programming)的模式。采用托付式者被動(dòng)等通知notification),行為取決于外來的突發(fā)事件,是事件驅(qū)動(dòng)的,符合事件驅(qū)動(dòng)式編程Event-Driven Programming,簡(jiǎn)稱EDP)的模式。下面我們就來說說這種編程范式。”

    逗號(hào)甕聲甕氣道:“沒想到打瞌睡打出了個(gè)范式。”

    冒號(hào)瞥了他一眼,繼續(xù)說下去:“為完成一樣事,既可以采用流程驅(qū)動(dòng)式,也可以采用事件驅(qū)動(dòng)式。這樣的例子在生活中可謂俯拾即是,剛才逗號(hào)同學(xué)為大家現(xiàn)場(chǎng)示范了一個(gè),誰還能舉出其他范例?”

    嘆號(hào)搶先舉例:“與客戶打交道,推銷員主動(dòng)打電話或登門拜訪,他的工作是流程驅(qū)動(dòng)的;接線員坐等電話,他的工作是事件驅(qū)動(dòng)的。”

    問號(hào)也說:“同樣是交通工具,公共汽車主要是流程驅(qū)動(dòng)的,它的路線已預(yù)先設(shè)定;出租車主要是事件驅(qū)動(dòng)的,它的路線基本上由隨機(jī)搭載的乘客所決定。”

    引號(hào)以個(gè)人經(jīng)驗(yàn)作例:“購買喜愛的雜志可以選擇頻繁光顧報(bào)刊亭,也可以選擇一次性訂閱。瀏覽關(guān)注的新聞網(wǎng)站或博客,可以直接訪問站點(diǎn),也可以訂閱相應(yīng)的RSS。主動(dòng)檢查所關(guān)心的內(nèi)容是否更新是流程驅(qū)動(dòng)的,用訂閱的方式是事件驅(qū)動(dòng)的。”

    句號(hào)回到本行:“Windows下的許多工作既可以在DOS下用批處理程序?qū)崿F(xiàn),也可以在圖形界面下完成。前者不需人工干預(yù),顯然是流程驅(qū)動(dòng)的;后者毫無疑問是事件驅(qū)動(dòng)的。”

    “看來你們對(duì)這種范式很熟悉嘛。不過,它原理雖簡(jiǎn)單,威力卻無窮。看似一招,實(shí)則暗藏百式,甚可幻化千招。個(gè)中精妙之處,斷非一時(shí)可以盡述。”冒號(hào)不知不覺中又走進(jìn)了武俠的世界。

    眾人聽了,暗疑老冒有些言過其實(shí)。

    冒號(hào)正式入題:“首當(dāng)其沖的問題是:何謂事件?通俗地說,它是已經(jīng)發(fā)生的某種令人關(guān)注的事情。在軟件中,它一般表現(xiàn)為一個(gè)程序的某些信息狀態(tài)上的變化。基于事件驅(qū)動(dòng)的系統(tǒng)一般提供兩類的內(nèi)建事件built-in event):一類是底層事件low-level event)或稱原生事件native event),在用戶圖形界面(GUI)系統(tǒng)中這類事件直接由鼠標(biāo)、鍵盤等硬件設(shè)備觸發(fā);一類是語義事件semantic event),一般代表用戶的行為邏輯,是若干底層事件的組合。比如鼠標(biāo)拖放(drag-and-drop)多表示移動(dòng)被拖放的對(duì)象,由鼠標(biāo)按下、鼠標(biāo)移動(dòng)和鼠標(biāo)釋放三個(gè)底層事件組成。”

    問號(hào)推想:“編程人員應(yīng)該還能創(chuàng)造新的事件類型吧?”

    “那是當(dāng)然。”冒號(hào)點(diǎn)點(diǎn)頭,“還有一類用戶自定義事件user-defined event)。它們可以是在原有的內(nèi)建事件的基礎(chǔ)上進(jìn)行的包裝,也可以是純粹的虛擬事件virtual event)。除此之外,編程者不但能定義事件,還能產(chǎn)生事件。雖然大部分事件是由外界激發(fā)的自然事件natural event),但有時(shí)程序員需要主動(dòng)激發(fā)一些事件,比如模擬用戶鼠標(biāo)點(diǎn)擊或鍵盤輸入等,這類事件被稱為合成事件synthetic event[1]。這些都進(jìn)一步豐富完善了事件體系和事件機(jī)制,使得事件驅(qū)動(dòng)式編程更具滲透性。”

    嘆號(hào)嘟噥了一句:“看來這里邊還有點(diǎn)名堂。”

    “名堂多著呢!”冒號(hào)回應(yīng),“事件固然是事件驅(qū)動(dòng)式編程的核心概念,但一個(gè)編程范式的獨(dú)特之處絕不僅僅是一些概念,更重要的是建立于這些概念之上的思維模式。為了了解這種范式與眾不同的特點(diǎn),我們先看看如何利用win32APIwindows下創(chuàng)建一個(gè)簡(jiǎn)單的窗口——”

    /** 一個(gè)win32窗口程序 */

    …WinMain(...) // windows應(yīng)用程序的主函數(shù)

    {

        // 第一步——注冊(cè)窗口類別

       ...;

       windowClass.lpfnWndProc = WndProc; // 指定該類窗口的回調(diào)函數(shù)

        windowClass.lpszClassName = windowClassName; // 指定該類窗口的名字

        RegisterClassEx(&windowClass);

        //第二步——?jiǎng)?chuàng)建一個(gè)上述類別的窗口

        CreateWindowEx(…, windowClassName, ...);

        …;

        //  第三步——消息循環(huán)

        while (GetMessage(&msg, NULL, 0, 0)  > 0) // 獲取消息

        {

            TranslateMessage(&msg); // 翻譯鍵盤消息

            DispatchMessage(&msg); // 分派消息

        }

    }

    // 第四步——窗口過程(處理消息)

    WndProc(…, msg,...)

    {

        switch (msg)

        {

            case WM_SIZE:   …;  // 用戶改變窗口尺寸

            case WM_MOVE: …; // 用戶移動(dòng)窗口

            case WM_CLOSE: …; // 用戶關(guān)閉窗口

    …;

        }

    }

    “沒有選用JavaVisual C++、C#、VB或者Delphi來實(shí)現(xiàn)窗口,是因?yàn)樗鼈兏叨鹊姆庋b和強(qiáng)大的IDE掩蓋了部分事件機(jī)制。如果你們對(duì)win32 API不太熟悉,沒有關(guān)系。為了減少語言和API上的障礙,同時(shí)突出重點(diǎn),這里最大限度地省略了次要的過程和參數(shù)等,僅保留脈絡(luò)主干。”冒號(hào)解釋,“從中看出到,創(chuàng)建一個(gè)能響應(yīng)用戶操作的win32窗口共分四步:注冊(cè)窗口類別、創(chuàng)建窗口、消息循環(huán)和窗口過程。”

    問號(hào)對(duì)概念很敏感:“消息與事件是一回事嗎?”

    “嚴(yán)格說來它們不是一回事,但如果你不想深究,不加區(qū)分也無大礙。概略地說,消息是Windows內(nèi)部最基本的通訊方式,事件需要通過消息來傳遞,是消息的主要來源。每當(dāng)用戶觸發(fā)一個(gè)事件,如移動(dòng)鼠標(biāo)或敲擊鍵盤,系統(tǒng)都會(huì)將其轉(zhuǎn)化為消息并放入相應(yīng)程序的消息隊(duì)列(message queue)中[2]。”冒號(hào)解答著,“明白了這一點(diǎn),上面的代碼就不難理解了——在消息循環(huán)中,程序通過GetMessage不斷地從消息隊(duì)列中獲取消息,經(jīng)過TranslateMessage預(yù)處理后再通過DispatchMessage將消息送交窗口過程WndProc處理。”

    逗號(hào)琢磨了一會(huì),不解地問:“窗口過程應(yīng)該是在分派消息時(shí)被調(diào)用的,但我怎么想不出DispatchMessage是如何聯(lián)系到WndProc的?”

    冒號(hào)為其解惑:“DispatchMessage的消息參數(shù)含有事發(fā)窗口的句柄handle),從而可以得到窗口過程WndProc[3]。至于窗口與窗口過程之間是如何建立聯(lián)系的,回看前面兩步就一目了然了:當(dāng)初在創(chuàng)建窗口時(shí)指明了窗口類別名windowClassName,而窗口類別windowClass又綁定了窗口過程。”

    嘆號(hào)有點(diǎn)納悶:“干嘛要繞這么大的彎子,直接調(diào)用WndProc不就得了?”

    “對(duì)于這個(gè)簡(jiǎn)單的程序來說,的確區(qū)別不大。但假如再增添其他菜單、按鈕、文本框之類的控件,每個(gè)控件都可綁定自己的窗口過程,那么到底該調(diào)用哪個(gè)才對(duì)呢?”冒號(hào)反問。

    嘆號(hào)雖有所悟,但仍有心結(jié):“總覺得窗口過程的用法有些怪怪的。”

    冒號(hào)一敲桌案:“沒錯(cuò)!怪就怪在編程者自己寫了一個(gè)應(yīng)用層的函數(shù),卻不直接調(diào)用它,而是通過庫函數(shù)間接調(diào)用。這類函數(shù)有個(gè)專用名稱:回調(diào)函數(shù)callback)。”

    引號(hào)忍不住插話:“回調(diào)函數(shù)我知道,在CC++中就是函數(shù)指針嘛。”

    “確切地說,函數(shù)指針是CC++用來實(shí)現(xiàn)callback的一種方式。此外,C++中的functorJava中的interfaceC#中的delegate都可實(shí)現(xiàn)callback。我們先圖解一下回調(diào)機(jī)制。”冒號(hào)調(diào)出一張圖示——

    “如果我們把系統(tǒng)劃分為兩層[4]:底層的函數(shù)庫和高層的應(yīng)用程序。同樣作為主函數(shù)的輔助函數(shù),左圖中的普通函數(shù)直接被主函數(shù)調(diào)用,然而右圖中的回調(diào)函數(shù)卻是通過庫函數(shù)間接被主函數(shù)調(diào)用的。”冒號(hào)的手影在幻燈下上下翻飛。

    句號(hào)點(diǎn)出要害:“一般都是高層代碼調(diào)用低層代碼,callback反其道而行之,因此顯得與眾不同。”

    “所言極是。一方面,在軟件模塊分層中,低層模塊為高層模塊提供服務(wù),但不能依賴高層模塊,以保證其可重用性和可擴(kuò)展性;另一方面,通常被調(diào)者(callee)為調(diào)用者(caller)提供服務(wù),調(diào)用者依賴被調(diào)者。兩相結(jié)合,決定了低層模塊多為被調(diào)者,高層模塊多為調(diào)用者。callback的出現(xiàn)改變了這種慣例,我們看一個(gè)簡(jiǎn)單的例子。”冒號(hào)寫下一段Java代碼——

    String[] strings = {"Please", "sort", "the", "strings", "in", "REVERSE", "order"};

    Arrays.sort(strings, new Comparator<String>() {

    public int compare(String a, String b){ return -a.compareToIgnoreCase(b); }

            });

    引號(hào)很快讀懂了代碼:“這是將字符串組不區(qū)分大小寫地逆序排列。其中Comparator的匿名類實(shí)現(xiàn)了callback,因?yàn)樗姆椒?/span>compare是在類庫中被調(diào)用的。”

    “此處callback的好處是顯而易見的——它使得Arrays.sort不再局限于自然排序,允許用戶自行定制排序規(guī)則,大大提高了算法的重用性。”冒號(hào)說著將幻燈片又翻到前頁,“回頭再看win32窗口程序的例子,其中第三步消息循環(huán)那段代碼不依賴應(yīng)用程序代碼,完全可以提煉出來作為library的一部分。事實(shí)上,在Visual C++里這段代碼就‘下放’到MFC類庫中去了。假設(shè)窗口過程由應(yīng)用程序直接調(diào)用,那么消息循環(huán)中的代碼將不再具有獨(dú)立性,無法作為公因子分解出來。”

    嘆號(hào)塊壘頓消,暢然無比:“終于搞清那個(gè)怪異的窗口過程了!每個(gè)窗口在創(chuàng)建時(shí)就攜帶了一個(gè)callback,以后每當(dāng)系統(tǒng)偵查到事件,都能輕易地從事發(fā)窗口身上找到它的callback,然后調(diào)用它以響應(yīng)事件。”

    “這等于將偵查事件與響應(yīng)事件兩項(xiàng)任務(wù)進(jìn)行了正交分解,降低了軟件的耦合度和復(fù)雜度。”句號(hào)言猶未盡,又加了一句,“就像剛才,引號(hào)負(fù)責(zé)偵查事件——警戒,逗號(hào)負(fù)責(zé)響應(yīng)事件——警醒。想法很好,可惜配合不夠默契,還是給人逮住了。”

    逗、引二人大窘,余者大笑。

    “仔細(xì)比較,以上兩個(gè)callback的用法還是稍有不同的。在字符串組排序中,callback在作為參數(shù)傳入底層的函數(shù)后,很快就在該函數(shù)體中被調(diào)用;在窗口程序中,callback則先被儲(chǔ)存起來,至于何時(shí)被調(diào)用完全是未定之?dāng)?shù)。用一句話概括:前者屬同步synchronous)回調(diào),后者屬異步asynchronous)回調(diào)。它們都使調(diào)用者不再依賴被調(diào)者,將二者從代碼上解耦,異步調(diào)用更將二者從時(shí)間上解耦。”冒號(hào)顯示出一副新圖——

    “圖中處于底層的軟件平臺(tái)是在win32 API的基礎(chǔ)上的改進(jìn)。不僅把主循環(huán)從應(yīng)用程序中沉淀下來,而且將儲(chǔ)存callback的過程封裝在一個(gè)注冊(cè)函數(shù)中,使得應(yīng)用程序代碼變得更簡(jiǎn)潔、健壯。同時(shí)我們看到,整個(gè)流程的控制權(quán)已經(jīng)從應(yīng)用程序的主程序轉(zhuǎn)移到底層平臺(tái)的主循環(huán)中,符合好萊塢原則。”冒號(hào)。

    逗號(hào)好奇地問:“什么是好萊塢原則?”

    don't call us, we'll call you.”冒號(hào)難得甩出一句洋文,“我很想畫蛇添足地在末尾加上單詞‘back’,這樣更容易理解callback的含義:‘call you back’。此話的背景大約是這樣的:一個(gè)藝人要想演出,需與好萊塢的經(jīng)紀(jì)公司聯(lián)系。由于幻想一朝成名的人太多,經(jīng)紀(jì)人總是牛氣十足,他們的口頭禪是:‘別打電話給我們,留下你的電話,有活干我們會(huì)打給你的’。”

    引號(hào)認(rèn)真地解析:“好萊塢經(jīng)紀(jì)公司相當(dāng)于一個(gè)背后運(yùn)作的軟件平臺(tái),藝人相當(dāng)于一個(gè)callback,‘留下你的電話’就是注冊(cè)callback,‘我們會(huì)打給你的’就是異步調(diào)用callback。”

    冒號(hào)接著補(bǔ)充:“‘別打電話給我們’意味著經(jīng)紀(jì)公司處于主導(dǎo)地位,藝人們處于受控狀態(tài),這便是控制反轉(zhuǎn)Inversion of Control,簡(jiǎn)稱IoC)。”

    問號(hào)聽著耳熟:“控制反轉(zhuǎn)?第一課談到框架時(shí)似乎提到過。”

    “沒錯(cuò),正是它!”冒號(hào)談興愈濃,“一般library中用到callback只是局部的控制反轉(zhuǎn),而frameworkIoC機(jī)制用到全局。程序員犧牲了對(duì)應(yīng)用程序流程的主導(dǎo)權(quán),換來的是更簡(jiǎn)潔的代碼和更高的生產(chǎn)效率。如果將編程譬比命題作文,不用framework的程序是一張可以自由寫作的白紙,library是作文素材庫;采用framework的程序是一篇成型的作文,作者只需填寫空白的詞語和段落即可。”

    嘆號(hào)為之一嘆:“唉,編程序變成了做填空題,真沒勁!

    “那你就多努力,爭(zhēng)取以后出填空題吧。”冒號(hào)笑著鼓勵(lì)他,“控制反轉(zhuǎn)不僅增強(qiáng)了framework在代碼和設(shè)計(jì)上的重用性,還極大地提高了framework的可擴(kuò)展性。這是因?yàn)?/span>framework的內(nèi)部運(yùn)轉(zhuǎn)機(jī)制雖是封閉的,但也開放了不少與外部相連的擴(kuò)展接口點(diǎn),類似插件(plugin)體系。如下圖所示——”

    引號(hào)聯(lián)想到另一個(gè)名詞:“我知道有個(gè)依賴反轉(zhuǎn),與控制反轉(zhuǎn)是一回事嗎?”

    冒號(hào)簡(jiǎn)答:“雖然不少人把它們看成同義詞,但依賴反轉(zhuǎn)原則Dependency-Inversion Principle,簡(jiǎn)稱DIP)更加具體——高層模塊不應(yīng)依賴底層模塊,它們應(yīng)依賴抽象;抽象不應(yīng)依賴細(xì)節(jié),細(xì)節(jié)應(yīng)依賴抽象。經(jīng)常相提并論的還有依賴注射Dependency Injection,簡(jiǎn)稱DI)——?jiǎng)討B(tài)地為一個(gè)軟件組件提供外部依賴。由于時(shí)間關(guān)系,不再詳加介紹。有一點(diǎn)可以看出,它們的主題是控制與依賴,目的是解耦,方法是反轉(zhuǎn)而實(shí)現(xiàn)這一切的關(guān)鍵是抽象接口。”

    “為什么說是抽象接口而不是前面所說的回調(diào)函數(shù)?”打過瞌睡的逗號(hào)現(xiàn)在似乎變得特別清醒。

    冒號(hào)予以說明:“回調(diào)函數(shù)的提法較為古老,多出現(xiàn)于過程式編程,抽象接口是更現(xiàn)代、更OO的說法。另外從字面上看,‘回調(diào)’強(qiáng)調(diào)的是行為方式——底層反調(diào)高層,而‘抽象接口’強(qiáng)調(diào)的是實(shí)現(xiàn)方式——正是由于接口具有抽象性,底層才能在調(diào)用它時(shí)無需慮及高層的具體細(xì)節(jié),從而實(shí)現(xiàn)控制反轉(zhuǎn)。”

    眾人細(xì)細(xì)品味著冒號(hào)的這番話。

    問號(hào)忽然驚覺:“我們是不是跑題了?本來是談事件驅(qū)動(dòng)式編程的,結(jié)果從callback談到控制反轉(zhuǎn),再到框架,現(xiàn)在又說起了抽象接口。”

    “事物是普遍聯(lián)系的嘛。”冒號(hào)扯了句哲學(xué)套話,“不諳熟callbackIoC機(jī)制,就不可能真正領(lǐng)會(huì)事件驅(qū)動(dòng)式編程的精髓。不過,也該回到中心主題了。我們通過win32 API用四步實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的窗口程序,與事件直接相關(guān)的有三步:實(shí)現(xiàn)事件處理器event handler)或事件監(jiān)聽器event listener);注冊(cè)事件處理器;實(shí)現(xiàn)事件循環(huán)event loop)。具體上,事件處理器負(fù)責(zé)處理事件,經(jīng)注冊(cè)方能在事發(fā)時(shí)收到通知;事件循環(huán)負(fù)責(zé)偵查事件、預(yù)處理事件、管理事件隊(duì)列和分派事件等,無事時(shí)默默等待,有事時(shí)立即響應(yīng),生命不息工作不止。在整個(gè)事件機(jī)制中,主循環(huán)好比心臟,事件處理器好比大腦,是最重要的兩類模塊。”

    句號(hào)指出:“在支持事件驅(qū)動(dòng)的開發(fā)環(huán)境中,主循環(huán)是現(xiàn)成的。許多IDE的圖形編輯器在程序員點(diǎn)擊控件后,還能自動(dòng)生成事件處理器的骨架代碼,連注冊(cè)的步驟也免除了。”

    冒號(hào)提醒他:“并不是總有這樣的好事,要知道事件驅(qū)動(dòng)式并不局限于GUI應(yīng)用,支持事件驅(qū)動(dòng)的開發(fā)環(huán)境也未必唾手可得。程序員有時(shí)必須自行設(shè)計(jì)整個(gè)事件系統(tǒng),他需要決定:采用事件驅(qū)動(dòng)式是否合適?如果合適,如何設(shè)計(jì)事件機(jī)制?其中包括事件定義、事件觸發(fā)、事件偵查、事件轉(zhuǎn)化、事件合并、事件調(diào)度、事件傳播、事件處理、事件連帶(event cascade[5]等等一系列問題。”

    嘆號(hào)扮著苦相說:“我的腦袋就是一個(gè)事件監(jiān)聽器,在聽到要面臨這么多的事件后,迅速作出反應(yīng)——大了一圈。”

    眾皆彎腰捧腹。

    “腦袋能變大是件好事啊,說明它伸縮性強(qiáng),相信用它來編的程序也是一樣。”冒號(hào)打著哈哈,“事件驅(qū)動(dòng)式的程序可伸縮性就很強(qiáng),知道為什么嗎?”

    逗號(hào)隨口說道:“不是因?yàn)槔没卣{(diào)函數(shù)實(shí)現(xiàn)了控制反轉(zhuǎn)嗎?”

    “非也非也。”冒號(hào)文縐縐地說,“軟件的可伸縮性scalability)一般指應(yīng)對(duì)工作量增長的能力,多出于性能方面的考量。而控制反轉(zhuǎn)的主要作用是降低模塊之間的依賴性,從而降低模塊的耦合度和復(fù)雜度,提高軟件的可重用性、柔韌性和可擴(kuò)展性,但對(duì)可伸縮性并無太大幫助。我們已經(jīng)看到,控制反轉(zhuǎn)導(dǎo)致了事件驅(qū)動(dòng)式編程的被動(dòng)性(passivity。此外,事件驅(qū)動(dòng)式還具有異步性(asynchrony的特征,這是由事件的不可預(yù)測(cè)性與隨機(jī)性決定的。如果一個(gè)應(yīng)用中存在一些該類特質(zhì)的因素,比如頻繁出現(xiàn)堵塞呼叫blocking call),不妨考慮將其包裝為事件。”

    問號(hào)打岔道:“什么是堵塞呼叫?”

    冒號(hào)作了個(gè)比方:“在高速公路上一輛車突然出故障停在路途,急調(diào)維修人員。如果現(xiàn)場(chǎng)修理,在修好之前所在車道是堵塞的,后面車輛無法通行。類似地,在程序中一些函數(shù)需要等待某些數(shù)據(jù)而不能立即返回[6],從而堵塞整個(gè)進(jìn)程。”

    引號(hào)道出常識(shí):“顯然更可取的修車做法是:先把車拖到路邊,修完后向其他車輛發(fā)出信號(hào),以便重回車道。”

    冒號(hào)趁熱打鐵:“同理,我們可以讓堵塞呼叫暫時(shí)脫離主進(jìn)程,事成之后再利用事件機(jī)制申請(qǐng)重返原進(jìn)程。相比第一種同步流程式的方案,這種異步事件式將連續(xù)的進(jìn)程中獨(dú)立且耗時(shí)的部分抽取出來,從而減少隨機(jī)因素造成的資源浪費(fèi),提高系統(tǒng)的性能和可伸縮性。”

    問號(hào)聽得仔細(xì):“為什么抽取的部分是‘獨(dú)立且耗時(shí)’,而不是‘隨機(jī)且耗時(shí)’?”

    “問得好!”冒號(hào)很欣賞他嚴(yán)謹(jǐn)?shù)膶W(xué)風(fēng),“再拿修車來說,第二種方案之所以可行有兩方面原因:一是修車耗時(shí),二是修車獨(dú)立。所謂獨(dú)立又有兩層含義:與車道獨(dú)立——修車時(shí)不必占用車道;與后車獨(dú)立——后面車輛不必恭候該車。如果一分鐘內(nèi)能修好,或者路邊沒有足夠空位,再或者后面車輛是故障車的隨行車,那么拖車方案均不成立。大家可以自己類比堵塞呼叫的情形,我就不再饒舌了。總之,獨(dú)立是異步的前提,耗時(shí)是異步的理由。至于隨機(jī)嘛,只是副產(chǎn)品,一個(gè)獨(dú)立且耗時(shí)的子過程,通常結(jié)束時(shí)間也是不可預(yù)期的。”

    眼見天色已晚,冒號(hào)趕忙換上最后一頁幻燈片——

    “上圖為一個(gè)典型的事件驅(qū)動(dòng)式模型。事件處理器事先在關(guān)注的事件源上注冊(cè),后者不定期地發(fā)表事件對(duì)象,經(jīng)過事件管理器的轉(zhuǎn)化(translate)、合并(coalesce)、排隊(duì)(enqueue)、分派(dispatch)等集中處理后,事件處理器接收到事件并對(duì)其進(jìn)行相應(yīng)處理。請(qǐng)注意事件處理器隨時(shí)可以注冊(cè)或注銷事件源,意味著二者之間的關(guān)系是動(dòng)態(tài)建立和解除的。”冒號(hào)在幻燈屏上指指點(diǎn)點(diǎn),“通過事件機(jī)制,事件源與事件處理器之間建立了松耦合多對(duì)多關(guān)系:一個(gè)事件源可以有多個(gè)處理器,一個(gè)處理器可以監(jiān)聽多個(gè)事件源。再換個(gè)角度,把事件處理器視為服務(wù)方,事件源視為客戶方,便是一個(gè)client-server模式。每個(gè)服務(wù)方與其客戶方之間的會(huì)話(session)是異步的,即在處理完一個(gè)客戶的請(qǐng)求后不必等待下一請(qǐng)求,隨時(shí)可切換(switch)到對(duì)其他客戶的服務(wù)。更有甚者,事件處理器也能產(chǎn)生事件,實(shí)現(xiàn)處理器接口的事件源也能處理事件,它們可以角色換位,于是又演化為peer-to-peer模式。”

    嘆號(hào)抱怨:“有點(diǎn)眼花繚亂了。”

    為濕潤枯燥的理論,冒號(hào)再次舉例:“你們不是很喜歡在QQ上聊天嗎?QQ服務(wù)器是事件管理器,每個(gè)聊天者既是事件源又是事件處理器,這正是事件驅(qū)動(dòng)式的P2P模式啊[7]。此外,聊天時(shí)不等對(duì)方回答,就可與另一網(wǎng)友交談,這就是會(huì)話切換帶來的異步效果。不過同樣是聊天,改用電話就稍有不同了。”

    冒號(hào)掃了 眾人一眼,果見有人皺起了眉頭。

    “當(dāng)你正用座機(jī)通話時(shí),手機(jī)響了。你會(huì)怎么做?”冒號(hào)提示。

    逗號(hào)本能地回答:“要么掛掉電話再接手機(jī),要么讓打手機(jī)的人遲些打來。”

    句號(hào)聽出了門道:“這說明電話的通話過程是同步而非異步的,原因是打電話雙方的交流是連貫的、非堵塞式的(non-blocking),與QQ聊天正好相反。”

    冒號(hào)點(diǎn)頭稱許。

    雖然早已過了下課時(shí)間,引號(hào)仍是好學(xué)不倦:“我覺得觀察者模式與事件驅(qū)動(dòng)式很像啊。”

    “你開始不是還舉了訂閱雜志和RSS的例子嗎?出版/訂閱(publish-subscribe模式[8]正是觀察者(observer模式的別名,一方面可看作簡(jiǎn)化或退化的事件驅(qū)動(dòng)式,另一方面可看作事件驅(qū)動(dòng)式的核心思想。該模式省略了事件管理器部分,由事件源直接調(diào)用事件處理器的接口。這樣更加簡(jiǎn)明易用,但威力有所削弱,缺少事件管理、事件連帶等機(jī)制。著名的MVCModel-View-Controller)模型正是它的一個(gè)應(yīng)用。”冒號(hào)長舒了一口氣,準(zhǔn)備收工,“事件驅(qū)動(dòng)式的應(yīng)用極廣,變化極多,還涉及到框架、設(shè)計(jì)模式、架構(gòu)、以及其他的編程范式,本身也可作為一種架構(gòu)模型。今天我們僅僅是蜻蜓點(diǎn)水,更深入更具體的內(nèi)容只能留后探討了。時(shí)候不早,你們也該餓了,趕快回家吧!范式可不能當(dāng)飯吃哦。”

    眾人笑作鳥獸散。

     
     插語

    [1] 許多基于事件驅(qū)動(dòng)的系統(tǒng)都提供了createEvent之類的API,授權(quán)編程者自行產(chǎn)生事件。

    [2] 更準(zhǔn)確地說,Windows先把所有的硬件事件存入系統(tǒng)消息隊(duì)列system message queue),然后再放入應(yīng)用程序消息隊(duì)列application message queue)。

    [3] 比如可以這樣從msg中得到窗口過程: (WNDPROC)GetWindowLong(msg.hwnd, GWL_WNDPROC)

    [4] 后面的論述同樣適用于其他形式的軟件分層結(jié)構(gòu)。

    [5] 指事件處理器在處理過程中又產(chǎn)生新的事件,從而再次觸發(fā)事件處理器。

    [6] 比如套接字(socket)中的accept函數(shù)

    [7] 真正的P2P網(wǎng)絡(luò)是不需要中心服務(wù)器的,此處P2P指聊天雙方是不分主客的對(duì)等關(guān)系。

    [8] 有人將出版-訂閱模式視為事件驅(qū)動(dòng)設(shè)計(jì)的同義詞,這是有道理的:在實(shí)際生活中,處于出版商與訂閱者之間的郵局可作為事件管理器。

     

     總結(jié)

    • 事件是程序中令人關(guān)注的信息狀態(tài)上變化。在基于事件驅(qū)動(dòng)的系統(tǒng)中,事件包括內(nèi)建事件與用戶自定義事件,其中內(nèi)建事件又分為底層事件和語義事件。此外,事件還有自然事件與合成事件之分。
    • Callback指能作為參數(shù)傳遞的函數(shù)或代碼,它允許底層模塊調(diào)用高層模塊,使調(diào)用者與被調(diào)者從代碼上解耦。異步callback在傳入后并不立即被調(diào)用,使調(diào)用者與被調(diào)者從時(shí)間上解耦。
    • 控制反轉(zhuǎn)一般通過callback來實(shí)現(xiàn),其目的是降低模塊之間的依賴性,從而降低模塊的耦合度和復(fù)雜度。
    • 在框架設(shè)計(jì)中,控制反轉(zhuǎn)增強(qiáng)了軟件的可重用性、柔韌性和可擴(kuò)展性,減少了用戶的負(fù)擔(dān),簡(jiǎn)化了用戶的代碼。
    • 控制反轉(zhuǎn)、依賴反轉(zhuǎn)原則和依賴注射是近義詞,它們的主題是控制與依賴,目的是解耦,方法是反轉(zhuǎn),而實(shí)現(xiàn)這一切的關(guān)鍵是抽象接口。
    • 事件驅(qū)動(dòng)式編程的三個(gè)步驟:實(shí)現(xiàn)事件處理器;注冊(cè)事件處理器;實(shí)現(xiàn)事件循環(huán)。
    • 異步過程在主程序中以非堵塞的機(jī)制運(yùn)行,即主程序不必等待該過程的返回就能繼續(xù)下一步。異步機(jī)制能減少隨機(jī)因素造成的資源浪費(fèi),提高系統(tǒng)的性能和可伸縮性。
    • 獨(dú)立是異步的前提,耗時(shí)是異步的理由。
    • 事件驅(qū)動(dòng)式最重要的兩個(gè)特征是被動(dòng)性和異步性。被動(dòng)性來自控制反轉(zhuǎn),異步性來自會(huì)話切換。
    • 觀察者模式又名出版/訂閱模式,既是事件驅(qū)動(dòng)式的簡(jiǎn)化,也是事件驅(qū)動(dòng)式的核心思想。MVC模型是觀察者模式的一個(gè)應(yīng)用。

     

    “”參考

    [1] WikipediaEvent-driven programminghttp://en.wikipedia.org/wiki/Event-driven

    [2] WikipediaCallback (computer science)http://en.wikipedia.org/wiki/Callback_(computer_science)

    [3] Charles PetzoldProgramming Windows5th ed.RedmondMicrosoft Press199941-70

    [4] Robert C. MartinAgile Software Development: Principles, Patterns, and Practices(影印版).北京:中國電力出版社,2003127-134

    [5] Martin FowlerInversion of Control Containers and the Dependency Injection patternhttp://martinfowler.com/articles/injection.html

    [6] Erich GammaRichard HelmRalph JohnsonJohn VlissidesDesign Patterns: Elements of Reusable Object-Oriented SoftwareBostonAddison-Wesley1994293-299



    課后思考

    • 了解C++中的STLJava中的 Collections Framework
    • 當(dāng)你成功構(gòu)想地并實(shí)現(xiàn)了一個(gè)算法,是否考慮過利用泛型編程來擴(kuò)大其適用范圍以提高其重用性?
    • 當(dāng)你發(fā)覺幾個(gè)模塊中有類似的算法,是否考慮過利用泛型思想進(jìn)行重構(gòu)?
    • 當(dāng)你發(fā)覺程序中有大量類似的代碼,是否考慮過用產(chǎn)生式編程來自動(dòng)生成它們?
    • 試著利用編譯器生成器(如ANTLR)自定義一種DSL,并用它來解決問題。
    • 你采用過AOP嗎?它有哪些優(yōu)缺點(diǎn)?
    • 如何合理地抽象出系統(tǒng)的橫切關(guān)注點(diǎn)?
    • 請(qǐng)對(duì)比流程驅(qū)動(dòng)式編程與事件驅(qū)動(dòng)式編程之間的差異,它們各自適合哪些應(yīng)用?
    • 你編寫的代碼是否有足夠的靈活性和可擴(kuò)展性?能否利用控制反轉(zhuǎn)原理?
    • 你的程序中是如何處理堵塞呼叫的?是否考慮過引入異步機(jī)制?

    posted on 2008-11-24 08:37 鄭暉 閱讀(4080) 評(píng)論(5)  編輯  收藏 所屬分類: 冒號(hào)課堂

    評(píng)論

    # re: 冒號(hào)課堂§3.4:事件驅(qū)動(dòng) 2008-11-24 10:18 于翔

    不錯(cuò)不錯(cuò),就是長了點(diǎn),呵呵  回復(fù)  更多評(píng)論   

    # re: 冒號(hào)課堂§3.4:事件驅(qū)動(dòng) 2008-11-24 20:58 Birdshover

    非常不錯(cuò)~~~  回復(fù)  更多評(píng)論   

    # re: 冒號(hào)課堂§3.4:事件驅(qū)動(dòng) 2014-02-18 22:28 Guan

    神文章,寫的太好了  回復(fù)  更多評(píng)論   

    # re: 冒號(hào)課堂§3.4:事件驅(qū)動(dòng) 2015-04-24 19:34 Cwenbin

    你好,多謝你的文章,正在學(xué)習(xí)中  回復(fù)  更多評(píng)論   

    # re: 冒號(hào)課堂§3.4:事件驅(qū)動(dòng) 2015-04-24 19:35 Cwenbin

    多謝你的博文,正在學(xué)習(xí)中  回復(fù)  更多評(píng)論   

    導(dǎo)航

    統(tǒng)計(jì)

    公告

    博客搬家:http://blog.zhenghui.org
    《冒號(hào)課堂》一書于2009年10月上市,詳情請(qǐng)見
    冒號(hào)課堂

    留言簿(17)

    隨筆分類(61)

    隨筆檔案(61)

    文章分類(1)

    文章檔案(1)

    最新隨筆

    積分與排名

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

    主站蜘蛛池模板: 久久国产高潮流白浆免费观看| 美美女高清毛片视频黄的一免费 | 亚洲日韩精品国产3区| 亚洲国产欧美一区二区三区| 免费国产草莓视频在线观看黄| A级毛片成人网站免费看| 日韩精品无码免费一区二区三区| 亚洲性线免费观看视频成熟| 国产网站免费观看| 亚洲亚洲人成综合网络| 亚洲美女一区二区三区| 亚洲中文字幕乱码AV波多JI| h片在线观看免费| 精品熟女少妇a∨免费久久| 女人18毛片特级一级免费视频| 亚洲国产日韩在线观频| 亚洲人成电影在在线观看网色| 在线综合亚洲中文精品| 一个人看的免费高清视频日本| 18女人毛片水真多免费| 免费一级特黄特色大片在线| 亚洲Aⅴ无码专区在线观看q| 亚洲日本在线电影| 国产午夜无码精品免费看| 无码高潮少妇毛多水多水免费| 亚洲视频在线一区二区| 亚洲免费在线观看视频| 一级做a爰片久久毛片免费陪| 4444www免费看| 亚洲国产精品人人做人人爽 | 亚洲成A人片777777| 亚洲欧美国产国产一区二区三区| jizz日本免费| 成人性生交大片免费看无遮挡| 久久久久亚洲AV成人网人人网站| 亚洲午夜电影在线观看高清| 久久精品无码免费不卡| 国内自产少妇自拍区免费| 亚洲AV无码成人精品区天堂| 日韩在线视精品在亚洲| 在线观看免费视频资源|