探討與比較Java和.NET的事件處理框架

發表于:2007-05-25來源:作者:點擊數: 標簽: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 aclearcase/" 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