熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

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

2022-06-13   來源: .NET編程 
事件驅動模型

  事件驅動模型是軟件系統平台中的一個重要區域現代軟件系統大量地使用事件驅動的處理方法尤其在用戶界面方面雖然如此過去在軟件開發語言中一直沒有融入事件處理的因子直的出現才將事件處理的工作負荷一部分的分派給編譯器從而稍微減輕開發者的負擔

  下圖顯示事件模型的組成份子



  Subscriber需事先和publisher預訂要接受其發布的某事件(下圖apublisher在某事件發生以後必需先生成該事件的相關數據對象(下圖a然後通過方法調用來通知subscriber(下圖a也就是用回調(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裡有javaawteventXXXAdapter抽象輔助類)另一個缺點則是必需要為每一類事件定義一個接口類型即使可能大部分的事件只有極少的方法

  微軟在為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
publisherKeyPressedSink = &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
publisherKeyPressed = 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) {
  listenerListadd(l);
 }
 public void fireKeyPressedEvent(int keyCode) {
  Iterator iter = erator();
  while (iterhasNext()) {
   KeyListener l = (KeyListener)iternext();
   lkeyPressed(keyCode);
  }
 }
}
  當然這段代碼只是簡單的示意如果要考慮多線程的安全問題可能要在addKeyListener()前面加上synchronized還有有預訂就必然相應的有退訂(removeXXXListener())在這裡就不再把它寫出來

  如果每個publisher都要這樣重復撰寫這樣的代碼的確很麻煩所以中勢必希望能夠提供一種用來幫助publisher記錄預訂者和發布事件的工具按一般設計者的初步想法一定是先提供一個輔助類來協助:



  從語法上考慮簡化add/remove動作應該可以用C++的operator=()operator+=()和operator=()來完成像這樣:

Publisher publisher = new Publisher();

publisherKeyEventHandlerDelegate += KeyPressedHandler;
//等同於
//publisherEventHandlerDelegateadd(KeyPressedHandler);
  如果可以這樣撰寫的話確實很簡單不過在強制性類型(stronglytyped)語言系統中必需精確的定義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 OnKeyPressed(int keyCode)
{
 
}

Publisher publisher = new Publisher();
publisherKeyPressed = OnKeyPressed;  //預訂
publisherKeyPressed += OnKeyPressed; //預訂另一個
  這樣一個delegate不僅可以幫忙記錄一個以上的subscribers也可以簡單的通過一行的調用語句來發布事件其實編譯器會為每一個delegate 生成一個相應的類來幫助處理這些工作不過是作為一個只是編寫應用系統的程序員是不必要去了解這些細節的有興趣的人可以去研究SystemDelegate和SystemMulticastDelegate 類

  上面看到的結果應該已經是比較滿意的但是仍有改善空間首先因為一個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();
// !!! 不允許 !!! 會編譯錯誤 !!!
publisherKeyPressed();
  接著event delegate是以一個成員變量的方式存在如果能以屬性的方式讓外界進行存取不是更好嗎於是又增加了event Accessors在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中推薦所有的事件數據類都繼承javautilEventObject 類因為在生成一個EventObject 對象的時候必需給一個event source 對象作為參數然後可以通過EventObject的getSource()方法來取得這個對象在EventObject裡面並沒有包含其他任何事件數據所以如果在事件的傳遞過程當中有任何事件數據需要傳遞就必需從EventObject 派生出一個新的子類出來如下圖



  當中也有一個相似的類叫SystemEventArgs但是這個類的內容是空的如下

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則仍保有其一貫簡約的風格
From:http://tw.wingwit.com/Article/program/net/201311/11899.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.