探討與比較Java和.NET的事件處理框架
事件驅動模型 事件驅動模型是軟件系統平臺中的一個重要區域,現代軟件系統大量地使用事件驅動的處理方法,尤其在用戶界面方面。雖然如此,過去在軟件 開發語言 中一直沒有融入事件處理的因子,直到.NET的出現,才將事件處理的工作負荷一部分的分派給編譯器,
事件驅動模型 事件驅動模型是軟件系統平臺中的一個重要區域,現代軟件系統大量地使用事件驅動的處理方法,尤其在用戶界面方面。雖然如此,過去在軟件
開發語言中一直沒有融入事件處理的因子,直到
.NET的出現,才將事件處理的工作負荷一部分的分派給編譯器,從而稍微減輕
開發者的負擔。
下圖顯示事件模型的組成份子:
Subscriber需事先和publisher預訂要接受其發布的某事件(下圖a1),publisher在某事件發生以后,必需先生成該事件的相關數據對象(下圖a2.1),然后通過方法調用來通知subscriber(下圖a2.2),也就是用回調(callback)的方式來通知subscriber。當然在預訂的時候,并不一定要由subscriber自身來預訂,也可以由另一個對象來幫忙預訂。其動態圖形示意如下:
本文并不探討異步的信息傳送,也就是在整個事件的處理過程當中,publisher和subscriber 對象皆需要同時存在。如果對于離線(offline)的方式來處理事件有興趣的話,請參閱
Java的JMS(
Java Message Service)和.NET的LCE(Loosely Coupled Events)。
事件是什么? 那么,到底事件是什么?在軟件系統中要如何表達一個事件?一個事件應該包括兩個東西:識別事件的名稱(event identity),和事件的相關的數據(event data)。例如,一個鍵盤按鍵被按下的事件可能叫KeyPressedEvent,事件數據則為該按鍵的代碼。
先前提到發布事件是用調用方法的方式(回調),不過有一個問題,就是publisher無法事先知道subscriber的類型。在Java的編碼模式當中,回調可以使用接口模式,也就是publisher必需事先定義好一個在發布事件中使用的接口,subscriber實現該接口中的方法,publisher則通過調用接口中的方法來完成發布事件的工作。如下圖:
這樣,在Java的編碼模式中,一個事件的識別名稱就是接口名稱和其中的方法名稱,而事件數據則自然是接口方法的參數了。Java對于這個接口的命名風格為XXXListener,顧名思義就是某事件的傾聽者。例如:
public interface KeyListener extends EventListener { public void keyTyped(KeyEvent e); public void keyPressed(KeyEvent e); public void keyReleased(KeyEvent e); } |
由于一個接口中可以包含多個方法,所以Java在設計事件的時候,是將一組相關聯的事件放在一起,這樣設計的優點是可以很好的將事件做分類,并且在publisher中如果要處理的事件較多的話,可以使用比較少的成員變量來記錄subscribers。缺點是如果subscriber只對事件接口中的部分事件有興趣,也必需要全盤實現該接口(所以在AWT里有
java.awt.event.XXXAdapter抽象輔助類)。另一個缺點則是必需要為每一類事件定義一個接口類型,即使可能大部分的事件只有極少的方法。
微軟在為C#語言命名的時候,就刻意隱喻C#是從C/
C++為基礎發展而得的
面向對象程序語言,始祖絕不是Java,所以肯定要保留一些C/C++的語言機制。在C/C++里面對回調的設計方式就是用函數指針,想當然C#也希望直接使用類似函數調用的方式來做為事件發布的方法。如下圖:
所以C#期望使用函數指針類型來作為事件的識別名稱,然后用函數的參數來傳遞事件數據。我們先用一段C++代碼來描繪這幅圖畫:
Event type definition: // 定義KeyPressedCallback 為一個函數指針的類型, // 該函數接受一個整數型參數,無返回值 typedef void (*KeyPressedCallback)(int keyCode);
Publisher: class Publisher { public KeyPressedCallback KeyPressedSink = null; ... void FireEvent(int KeyCode) { if (KeyPressedSink != null) (*KeyPressedSink)(keyCode);//callback } }
Subscriber: void KeyPressedHandler(int keyCode) { ... } ... Publisher publisher = new Publisher(); //register publisher.KeyPressedSink = &KeyPressedHandler; |
一個當代的純面向對象程序語言,是肯定希望要把造成程序復雜和不易維護的指針給去除的。所以在C#語言機制當中,勢必要創造新的元素來取代,于是delegate(委托)出現了。如下:
Event type definition:
// 定義KeyPressedDelegate 為一個類似函數指針的類型, // 該函數接受一個整數型參數,無返回值 delegate void KeyPressedDelegate(int keyCode);
Publisher: class Publisher { public KeyPressedDelegate KeyPressed = null; ... void FireEvent(int KeyCode) { if (KeyPressed != null) KeyPressed(keyCode); } }
Subscriber: void KeyPressedHandler(int keyCode) { ... } ... Publisher publisher = new Publisher(); //register publisher.KeyPressed = KeyPressedHandler; |
一開始,你可以把KeyPressedDelegate當成是與函數指針相類似的東西,通過它你可以引用一個實例方法或靜態方法,就好像引用一個對象一樣。然后可以通過這個delegate直接調用其引用的方法。但是下面你會看到delegate更擴大了其引用能力。
事件的預訂和發布 Publisher必需能夠接受多個subscribers的預訂,所以在publisher當中必需維護預訂者的列表以供將來發布事件使用。在Java語言的模式中,并沒有提供特別的東西來幫助這件事,可以自己用collection 類來做,例如可以使用ArrayList 對象來做記錄。Java的預訂方法名的風格為addXXXListener(),因為在publisher端必需把subscriber 對象“添加”到預訂者列表后面,如下圖:
對于subscriber來說,預訂動作的內部處理是黑箱的,subscriber不用關心publisher是如何做預訂記錄的。參考以下代碼片段:
class Publisher { private ArrayList listenerList = new ArrayList(); public void addKeyListener(KeyListener l) { listenerList.add(l); } public void fireKeyPressedEvent(int keyCode) { Iterator iter = listenerList.iterator(); while (iter.hasNext()) { KeyListener l = (KeyListener)iter.next(); l.keyPressed(keyCode); } } } |
當然這段代碼只是簡單的示意,如果要考慮多線程的
安全問題,可能要在addKeyListener()前面加上synchronized;還有,有預訂就必然相應的有“退訂”(removeXXXListener()),在這里就不再把它寫出來。
如果每個publisher都要這樣重復撰寫這樣的代碼的確很麻煩。所以在.NET中勢必希望能夠提供一種用來幫助publisher記錄預訂者,和發布事件的工具。按一般設計者的初步想法,一定是先提供一個輔助類來協助:
從語法上考慮簡化,add/remove動作應該可以用C++的operator=()、operator+=()和operator-=()來完成。像這樣:
Publisher publisher = new Publisher(); ... publisher.KeyEventHandlerDelegate += KeyPressedHandler; //等同于 //publisher.EventHandlerDelegate.add(KeyPressedHandler); |
如果可以這樣撰寫的話,確實很簡單。不過在強制性類型(strongly-typed)語言系統中,必需精確的定義add()方法參數中的delegate 類型,這樣似乎無法寫出一個可以公用的基礎類。所以在.NET中,是結合delegate關鍵字,通過簡單的語法,借助編譯器來幫我們自動生成相關的代碼。于是把delegate的能力再予以加強了:
Event type definition: public delegate void KeyPressedDelegate(int keyCode);
Publisher: class Publisher { public KeyPressedDelegate KeyPressed; ... void FireKeyPressedEvent(int KeyCode) { if (KeyPressed != null) //依次調用記錄在KeyPressed中的所有方法 KeyPressed(keyCode); } }
Subscriber: void OnKeyPressed(int keyCode) { ... } void OnKeyPressed2(int keyCode) { ... } ... Publisher publisher = new Publisher(); publisher.KeyPressed = OnKeyPressed; //預訂 publisher.KeyPressed += OnKeyPressed2; //預訂另一個 |
這樣一個delegate不僅可以幫忙記錄一個以上的subscribers,也可以簡單的通過一行的調用語句來發布事件。其實編譯器會為每一個delegate 生成一個相應的類來幫助處理這些工作,不過是作為一個只是編寫應用系統的
程序員是不必要去了解這些細節的,有興趣的人可以去研究System.Delegate和System.MulticastDelegate 類。
上面看到的結果應該已經是比較滿意的,但是仍有改善空間。首先,因為一個delegate成員是public的,任何人都可以任意的直接接觸,有失面向對象世界中的信息封裝和隱藏(information encapsulation and hiding)的原則。所以在C#中又增加一個關鍵字“event”,用在放在聲明一個delegate成員變量的前面,這樣表示只有在聲明這個delegate的類內部才可以直接對它進行subscriber 調用。
public delegate void KeyPressedDelegate(int keyCode); class Publisher { public event KeyPressedDelegate KeyPressed; ... void FireKeyPressedEvent(int KeyCode) { if (KeyPressed != null) //只有在Publisher才可以 KeyPressed(keyCode); } }
// outside of Publisher... Publisher publisher = new Publisher(); // !!! 不允許 !!! 會編譯錯誤 !!! publisher.KeyPressed(100); |
接著,event delegate是以一個成員變量的方式存在,如果能以屬性的方式讓外界進行存取,不是更好嗎。于是又增加了event a
clearcase/" target="_blank" >ccessors。在C#語言中,是使用add和remove來封裝實際的 += 和 -= 操作。如下:
class Publisher { protected event KeyPressedDelegate m_KeyPressed;
// event accessor。定義一個事件屬性。 public event KeyPressedDelegate KeyPressed { add { m_KeyPressed += value; } remove { m_KeyPressed -= value; } } void FireKeyPressedEvent(int KeyCode) { if (KeyPressed != null) m_KeyPressed(keyCode); } } |
不管是事件變量或者是事件屬性,對聲明事件變量和屬性的類的外部,只能對它做 += 和 -= 操作。這樣可以加強它的
安全性。當然event accessor只有add和remove操作,所以不管是任何人(包括聲明該事件屬性的類內部),也只能對事件屬性做 += 和 -= 操作。
經過這樣的改善,可以理論上更減弱publisher和subscriber之間的耦合力了。
事件數據 接下來我們談一談另一個在事件模型中的重要角色,就是在事件發布中被傳遞的“事件數據”。
一個subscriber在接受同一種事件的時候,可能來自不同的publisher,所以自然地希望知道發出事件的人是誰,也就是在傳遞的參數當中,必需包含一個publisher 對象的引用。在Java中,推薦所有的事件數據類都繼承
java.util.EventObject 類。因為在生成一個EventObject 對象的時候,必需給一個event source 對象作為參數。然后可以通過EventObject的getSource()方法來取得這個對象。在EventObject里面,并沒有包含其他任何事件數據,所以如果在事件的傳遞過程當中,有任何事件數據需要傳遞,就必需從EventObject 派生出一個新的子類出來。如下圖:
在.NET當中也有一個相似的類叫System.EventArgs,但是這個類的內容是空的,如下:
public class EventArgs { public static readonly EventArgs Empty; static EventArgs() { Empty = new EventArgs(); } public EventArgs() {} } |
.NET認為不一定所有的subscriber都對event source感興趣,所以如果需要的話,就把event source當成是delegate方法的參數來傳遞好了。.NET定義了一個標準的delegate EventHandler,以下是它的簽名(signature):
public delegate void EventHandler(object sender, EventArgs e); |
以后,只要你需要的delegate的簽名與EventHandler相同的話,就直接用它了。這里所謂的簽名相同,是指參數的類型和返回值的類型皆相同。
Java和.NET都希望用戶在定義的事件數據類的時候,盡可能的使用推薦的基類,因為這樣在publisher對發出的事件數據內容有所變更或擴大的時候,對subscriber的沖擊會比較小,這是由于多型(polymorphism)機制的幫助。
結束語 經過這番解析之后,應該能夠比較清楚的了解到Java和.NET事件處理框架的設計思路,希望有助于讀者更進一步理解其框架的形成過程。從語言的角度來看,.NET的確有一些針對性的改善和試圖簡化對事件的處理,Java則仍保有其一貫簡約的風格。讀者若有任何意見和指教,可以通過e-mail與我交流:bruceyou@sina100.com。
原文轉自:http://www.anti-gravitydesign.com
- 評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
-
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97
|