我對事件驅動的理解

axgle發表於2006-07-10
我對事件驅動的理解
作者: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修改過]

相關文章