我對事件驅動的理解

我對事件驅動的理解
作者: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)建立事件驅動機制的辦法。而在面向對象的語言中,依然靠的是回調的方式,只不過傳遞的不是‘回調函數名’,而是傳遞的類名,而類中其實
已經綁定了方法(本質上依然是函數)。因此可以說面向對象的語言依然使用的是回調函數的方式建立的事件驅動的機制。


結語:

事件驅動依照“事件-響應”的模式,通過回調的方式執行自定義的響應函數,保持了事件和程序邏輯的穩定性和擴展的開放性,從而爲程序的開發和維護提供了有力的幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章