程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 冒號課堂§3.4:事件驅動

冒號課堂§3.4:事件驅動

編輯:關於JAVA

第三課 常用范式(4)

3.4事件驅動——有事我叫你,沒事別煩我

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

關鍵詞:編程范式,事件驅動式,回調函數,framework,IoC,DIP,觀察者模式

摘要:事件驅動式編程簡談

?提問

什麼是事件?有哪些不同類型的事件?

什麼是回調函數?什麼是異步同調?它們有什麼用處?

控制反轉的目的是什麼?它是如何實現的?在框架設計中起什麼作用?

控制反轉、依賴反轉原則和依賴注射的共同點是什麼?

事件驅動式編程有哪些關鍵步驟?

異步過程特點和作用是什麼?

事件驅動式編程最重要的特征是什麼?它們是如何實現的?

事件驅動式與觀察者模式、MVC模型有何關系?

:講解

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

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

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

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

“總之是很雷人。”歎號的網絡新語再度引發笑聲。

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

“第一種方式睡不踏實,不得已而為之。敢用第二種方式的人多半沒心沒肺,估計IT人都達不到那種境界。只要有同伴在身旁,我想大家都會選第三種方式的。”句號的回答獲得一致認同。

冒號續問:“好,拋開第二種方式不談,為什麼第三種要比第一種優越呢?”

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

冒號再問:“他們是如何合作的呢?”

“放哨者一旦發現有情況,立即通知犯困者采取行動——睜眼坐直,作認真聽講狀。”句號說得是繪聲繪色。

除了兩位當事人略顯尴尬外,其他人均樂不可支。

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

逗號甕聲甕氣道:“沒想到打瞌睡打出了個范式。”

冒號瞥了他一眼,繼續說下去:“為完成一樣事,既可以采用流程驅動式,也可以采用事件驅動式。這樣的例子在生活中可謂俯拾即是,剛才逗號同學為大家現場示范了一個,誰還能舉出其他范例?”

歎號搶先舉例:“與客戶打交道,推銷員主動打電話或登門拜訪,他的工作是流程驅動的;接線員坐等電話,他的工作是事件驅動的。”

問號也說:“同樣是交通工具,公共汽車主要是流程驅動的,它的路線已預先設定;出租車主要是事件驅動的,它的路線基本上由隨機搭載的乘客所決定。”

引號以個人經驗作例:“購買喜愛的雜志可以選擇頻繁光顧報刊亭,也可以選擇一次性訂閱。浏覽關注的新聞網站或博客,可以直接訪問站點,也可以訂閱相應的RSS。主動檢查所關心的內容是否更新是流程驅動的,用訂閱的方式是事件驅動的。”

句號回到本行:“Windows下的許多工作既可以在DOS下用批處理程序實現,也可以在圖形界面下完成。前者不需人工干預,顯然是流程驅動的;後者毫無疑問是事件驅動的。”

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

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

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

問號推想:“編程人員應該還能創造新的事件類型吧?”

“那是當然。”冒號點點頭,“還有一類用戶自定義事件(user-defined event)。它們可以是在原有的內建事件的基礎上進行的包裝,也可以是純粹的虛擬事件(virtual event)。除此之外,編程者不但能定義事件,還能產生事件。雖然大部分事件是由外界激發的自然事件(natural event),但有時程序員需要主動激發一些事件,比如模擬用戶鼠標點擊或鍵盤輸入等,這類事件被稱為合成事件(synthetic event)[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)
   {
     case WM_SIZE:  …; // 用戶改變窗口尺寸
     case WM_MOVE: …; // 用戶移動窗口
     case WM_CLOSE: …; // 用戶關閉窗口
…;
   }
}

“沒有選用Java、Visual C++、C#、VB或者Delphi來實現窗口,是因為它們高度的封裝和強大的IDE掩蓋了部分事件機制。如果你們對win32 API不太熟悉,沒有關系。為了減少語言和API上的障礙,同時突出重點,這裡最大限度地省略了次要的過程和參數等,僅保留脈絡主干。”冒號解釋,“從中看出到,創建一個能響應用戶操作的win32窗口共分四步:注冊窗口類別、創建窗口、消息循環和窗口過程。”

問號對概念很敏感:“消息與事件是一回事嗎?”

“嚴格說來它們不是一回事,但如果你不想深究,不加區分也無大礙。概略地說,消息是Windows內部最基本的通訊方式,事件需要通過消息來傳遞,是消息的主要來源。每當用戶觸發一個事件,如移動鼠標或敲擊鍵盤,系統都會將其轉化為消息並放入相應程序的消息隊列(message queue)中[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, new Comparator<String>() {
public int compare(String a, String b){ return -a.compareToIgnoreCase(b); }
     });

引號很快讀懂了代碼:“這是將字符串組不區分大小寫地逆序排列。其中Comparator的匿名類實現了callback,因為它的方法compare是在類庫中被調用的。”

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

歎號塊壘頓消,暢然無比:“終於搞清那個怪異的窗口過程了!每個窗口在創建時就攜帶了一個callback,以後每當系統偵查到事件,都能輕易地從事發窗口身上找到它的callback,然後調用它以響應事件。”

“這等於將偵查事件與響應事件兩項任務進行了正交分解,降低了軟件的耦合度和復雜度。”句號言猶未盡,又加了一句,“就像剛才,引號負責偵查事件——警戒,逗號負責響應事件——警醒。想法很好,可惜配合不夠默契,還是給人逮住了。”

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

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

“圖中處於底層的軟件平台是在win32 API的基礎上的改進。不僅把主循環從應用程序中沉澱下來,而且將儲存callback的過程封裝在一個注冊函數中,使得應用程序代碼變得更簡潔、健壯。同時我們看到,整個流程的控制權已經從應用程序的主程序轉移到底層平台的主循環中,符合好萊塢原則。”冒號。

逗號好奇地問:“什麼是好萊塢原則?”

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

引號認真地解析:“好萊塢經紀公司相當於一個背後運作的軟件平台,藝人相當於一個callback,‘留下你的電話’就是注冊callback,‘我們會打給你的’就是異步調用callback。”

冒號接著補充:“‘別打電話給我們’意味著經紀公司處於主導地位,藝人們處於受控狀態,這便是控制反轉(Inversion of Control,簡稱IoC)。”

問號聽著耳熟:“控制反轉?第一課談到框架時似乎提到過。”

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

歎號為之一歎:“唉,編程序變成了做填空題,真沒勁! ”

“那你就多努力,爭取以後出填空題吧。”冒號笑著鼓勵他,“控制反轉不僅增強了framework在代碼和設計上的重用性,還極大地提高了framework的可擴展性。這是因為framework的內部運轉機制雖是封閉的,但也開放了不少與外部相連的擴展接口點,類似插件(plugin)體系。如下圖所示——”

引號聯想到另一個名詞:“我知道有個依賴反轉,與控制反轉是一回事嗎?”

冒號簡答:“雖然不少人把它們看成同義詞,但依賴反轉原則(Dependency-Inversion Principle,簡稱DIP)更加具體——高層模塊不應依賴底層模塊,它們應依賴抽象;抽象不應依賴細節,細節應依賴抽象。經常相提並論的還有依賴注射(Dependency Injection,簡稱DI)——動態地為一個軟件組件提供外部依賴。由於時間關系,不再詳加介紹。有一點可以看出,它們的主題是控制與依賴,目的是解耦,方法是反轉,而實現這一切的關鍵是抽象接口。”

“為什麼說是抽象接口而不是前面所說的回調函數?”打過瞌睡的逗號現在似乎變得特別清醒。

冒號予以說明:“回調函數的提法較為古老,多出現於過程式編程,抽象接口是更現代、更OO的說法。另外從字面上看,‘回調’強調的是行為方式——底層反調高層,而‘抽象接口’強調的是實現方式——正是由於接口具有抽象性,底層才能在調用它時無需慮及高層的具體細節,從而實現控制反轉。”

眾人細細品味著冒號的這番話。

問號忽然驚覺:“我們是不是跑題了?本來是談事件驅動式編程的,結果從callback談到控制反轉,再到框架,現在又說起了抽象接口。”

“事物是普遍聯系的嘛。”冒號扯了句哲學套話,“不谙熟callback和IoC機制,就不可能真正領會事件驅動式編程的精髓。不過,也該回到中心主題了。我們通過win32 API用四步實現了一個簡單的窗口程序,與事件直接相關的有三步:實現事件處理器(event handler)或事件監聽器(event listener);注冊事件處理器;實現事件循環(event loop)。具體上,事件處理器負責處理事件,經注冊方能在事發時收到通知;事件循環負責偵查事件、預處理事件、管理事件隊列和分派事件等,無事時默默等待,有事時立即響應,生命不息工作不止。在整個事件機制中,主循環好比心髒,事件處理器好比大腦,是最重要的兩類模塊。”

句號指出:“在支持事件驅動的開發環境中,主循環是現成的。許多IDE的圖形編輯器在程序員點擊控件後,還能自動生成事件處理器的骨架代碼,連注冊的步驟也免除了。”

冒號提醒他:“並不是總有這樣的好事,要知道事件驅動式並不局限於GUI應用,支持事件驅動的開發環境也未必唾手可得。程序員有時必須自行設計整個事件系統,他需要決定:采用事件驅動式是否合適?如果合適,如何設計事件機制?其中包括事件定義、事件觸發、事件偵查、事件轉化、事件合並、事件調度、事件傳播、事件處理、事件連帶(event cascade)[5]等等一系列問題。”

歎號扮著苦相說:“我的腦袋就是一個事件監聽器,在聽到要面臨這麼多的事件後,迅速作出反應——大了一圈。”

眾皆彎腰捧腹。

“腦袋能變大是件好事啊,說明它伸縮性強,相信用它來編的程序也是一樣。”冒號打著哈哈,“事件驅動式的程序可伸縮性就很強,知道為什麼嗎?”

逗號隨口說道:“不是因為利用回調函數實現了控制反轉嗎?”

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

問號打岔道:“什麼是堵塞呼叫?”

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

引號道出常識:“顯然更可取的修車做法是:先把車拖到路邊,修完後向其他車輛發出信號,以便重回車道。”

冒號趁熱打鐵:“同理,我們可以讓堵塞呼叫暫時脫離主進程,事成之後再利用事件機制申請重返原進程。相比第一種同步流程式的方案,這種異步事件式將連續的進程中獨立且耗時的部分抽取出來,從而減少隨機因素造成的資源浪費,提高系統的性能和可伸縮性。”

問號聽得仔細:“為什麼抽取的部分是‘獨立且耗時’,而不是‘隨機且耗時’?”

“問得好!”冒號很欣賞他嚴謹的學風,“再拿修車來說,第二種方案之所以可行有兩方面原因:一是修車耗時,二是修車獨立。所謂獨立又有兩層含義:與車道獨立——修車時不必占用車道;與後車獨立——後面車輛不必恭候該車。如果一分鐘內能修好,或者路邊沒有足夠空位,再或者後面車輛是故障車的隨行車,那麼拖車方案均不成立。大家可以自己類比堵塞呼叫的情形,我就不再饒舌了。總之,獨立是異步的前提,耗時是異步的理由。至於隨機嘛,只是副產品,一個獨立且耗時的子過程,通常結束時間也是不可預期的。”

眼見天色已晚,冒號趕忙換上最後一頁幻燈片——

“上圖為一個典型的事件驅動式模型。事件處理器事先在關注的事件源上注冊,後者不定期地發表事件對象,經過事件管理器的轉化(translate)、合並(coalesce)、排隊(enqueue)、分派(dispatch)等集中處理後,事件處理器接收到事件並對其進行相應處理。請注意事件處理器隨時可以注冊或注銷事件源,意味著二者之間的關系是動態建立和解除的。”冒號在幻燈屏上指指點點,“通過事件機制,事件源與事件處理器之間建立了松耦合的多對多關系:一個事件源可以有多個處理器,一個處理器可以監聽多個事件源。再換個角度,把事件處理器視為服務方,事件源視為客戶方,便是一個client-server模式。每個服務方與其客戶方之間的會話(session)是異步的,即在處理完一個客戶的請求後不必等待下一請求,隨時可切換(switch)到對其他客戶的服務。更有甚者,事件處理器也能產生事件,實現處理器接口的事件源也能處理事件,它們可以角色換位,於是又演化為peer-to-peer模式。”

歎號抱怨:“有點眼花缭亂了。”

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

冒號掃了 眾人一眼,果見有人皺起了眉頭。

“當你正用座機通話時,手機響了。你會怎麼做?”冒號提示。

逗號本能地回答:“要麼掛掉電話再接手機,要麼讓打手機的人遲些打來。”

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

冒號點頭稱許。

雖然早已過了下課時間,引號仍是好學不倦:“我覺得觀察者模式與事件驅動式很像啊。”

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

眾人笑作鳥獸散。

,插語

[1] 許多基於事件驅動的系統都提供了createEvent之類的API,授權編程者自行產生事件。

[2] 更准確地說,Windows先把所有的硬件事件存入系統消息隊列(system message queue),然後再放入應用程序消息隊列(application message queue)。

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

[4] 後面的論述同樣適用於其他形式的軟件分層結構。

[5] 指事件處理器在處理過程中又產生新的事件,從而再次觸發事件處理器。

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

[7] 真正的P2P網絡是不需要中心服務器的,此處P2P指聊天雙方是不分主客的對等關系。

[8] 有人將出版-訂閱模式視為事件驅動設計的同義詞,這是有道理的:在實際生活中,處於出版商與訂閱者之間的郵局可作為事件管理器。

。總結

事件是程序中令人關注的信息狀態上變化。在基於事件驅動的系統中,事件包括內建事件與用戶自定義事件,其中內建事件又分為底層事件和語義事件。此外,事件還有自然事件與合成事件之分。

Callback指能作為參數傳遞的函數或代碼,它允許底層模塊調用高層模塊,使調用者與被調者從代碼上解耦。異步callback在傳入後並不立即被調用,使調用者與被調者從時間上解耦。

控制反轉一般通過callback來實現,其目的是降低模塊之間的依賴性,從而降低模塊的耦合度和復雜度。

在框架設計中,控制反轉增強了軟件的可重用性、柔韌性和可擴展性,減少了用戶的負擔,簡化了用戶的代碼。

控制反轉、依賴反轉原則和依賴注射是近義詞,它們的主題是控制與依賴,目的是解耦,方法是反轉,而實現這一切的關鍵是抽象接口。

事件驅動式編程的三個步驟:實現事件處理器;注冊事件處理器;實現事件循環。

異步過程在主程序中以非堵塞的機制運行,即主程序不必等待該過程的返回就能繼續下一步。異步機制能減少隨機因素造成的資源浪費,提高系統的性能和可伸縮性。

獨立是異步的前提,耗時是異步的理由。

事件驅動式最重要的兩個特征是被動性和異步性。被動性來自控制反轉,異步性來自會話切換。

觀察者模式又名出版/訂閱模式,既是事件驅動式的簡化,也是事件驅動式的核心思想。MVC模型是觀察者模式的一個應用。

“”參考

[1] Wikipedia.Event-driven programming.http://en.wikipedia.org/wiki/Event-driven

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

[3] Charles Petzold.Programming Windows,5th ed..Redmond:Microsoft Press,1999.41-70

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

[5] Martin Fowler.Inversion of Control Containers and the Dependency Injection pattern.http://martinfowler.com/articles/injection.html

[6] Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides.Design Patterns: Elements of Reusable Object-Oriented Software.Boston:Addison-Wesley,1994.293-299

課後思考

了解C++中的STL和Java中的 Collections Framework。

當你成功構想地並實現了一個算法,是否考慮過利用泛型編程來擴大其適用范圍以提高其重用性?

當你發覺幾個模塊中有類似的算法,是否考慮過利用泛型思想進行重構?

當你發覺程序中有大量類似的代碼,是否考慮過用產生式編程來自動生成它們?

試著利用編譯器生成器(如ANTLR)自定義一種DSL,並用它來解決問題。

你采用過AOP嗎?它有哪些優缺點?

如何合理地抽象出系統的橫切關注點?

請對比流程驅動式編程與事件驅動式編程之間的差異,它們各自適合哪些應用?

你編寫的代碼是否有足夠的靈活性和可擴展性?能否利用控制反轉原理?

你的程序中是如何處理堵塞呼叫的?是否考慮過引入異步機制?

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved