事件聚合器

將多個物件的通道事件傳遞至單一物件,以簡化客戶端的註冊程序。

2004 年 9 月 29 日

這是 進階企業應用程式架構開發 撰寫的一部分,我是在 2000 年代中期撰寫的。很遺憾,自此之後,有太多其他事情吸引了我的注意力,因此我沒有時間進一步研究它們,而且在可預見的未來,我也看不到太多時間。因此,這份資料非常草稿化,而且在我有時間再次研究它之前,我不會進行任何更正或更新。

當客戶端想要訂閱事件時,具有大量物件的系統可能會導致複雜性。客戶端必須個別尋找並註冊每個物件,如果每個物件有多個事件,則每個事件都需要個別訂閱。

事件聚合器作為許多物件的單一事件來源。它註冊所有物件的所有事件,讓客戶端只需向聚合器註冊即可。

運作方式

事件聚合器是一個簡單的間接元素。在最簡單的形式中,您讓它向您感興趣的所有來源物件註冊,並讓所有目標物件向 事件聚合器 註冊。事件聚合器 會透過將事件傳播至目標物件來回應來源物件的任何事件。

最簡單的 事件聚合器 將來自多個物件的事件聚合到自身,並將相同的事件傳遞給其觀察者。事件聚合器 也能概括事件,將特定於來源物件的事件轉換為更通用的事件。這樣一來,聚合器的觀察者就不需要註冊這麼多個別事件類型。這簡化了觀察者的註冊程序,但代價是會收到可能對觀察者沒有任何實質影響的事件通知。

由於事件聚合器是基於觀察者,因此考慮觀察者的所有危險區域非常重要。

何時使用

事件聚合器在有許多物件是潛在事件來源時,是一個不錯的選擇。您可以將註冊邏輯集中到事件聚合器,而不是讓觀察者處理向所有物件註冊。除了簡化註冊外,事件聚合器也簡化了使用觀察者中的記憶體管理問題。

延伸閱讀

您可以將事件聚合器視為門面的特定形式,它只專注於觀察者關係。

範例:觀察我們的顧問 (C#)

當有人加入 Thoughtworks 時,他們會立即獲得一支手機。過去的玩笑是,這是因為 Roy (執行長) 希望能夠在任何時間撥打電話給您。因此,我在這裡想像一個簡單的應用程式,它會告訴 Roy 所有顧問的位置,以及他們是否可以接聽電話。

圖 1:顧問類別

為了擷取這個,我們有顧問物件,如圖 1所示。當顧問移動時,我們會透過關於這些移動的訊息來擷取這個,指出哪位顧問移動以及他們現在在哪裡。

當系統收到這些訊息時,它們會傳遞給顧問物件以處理它們並更新顧問的狀態。更新目前位置相當明顯,更新可用性有一些輕微的運算,基本上每個顧問都有家,並且可以在家時選擇是否可用。(假設在路上時,他們總是會受到 Roy 的電話奇想影響。)

class Consultant...

  public bool IsAvailable {
    get {return IsAvailableAt(_current_location);}
  }
  private bool IsAvailableAt(string location) {
    return (location == Home) ? _availableAtHome : true;
  }

因此,當顧問處理移動訊息時,它必須擔心兩件事可能改變 - 目前位置和可用性。它會為每個事件發出一個獨立的事件。

class Consultant...

  public void HandleMovement(MsgMovement movement) {
    if (_current_location != movement.Place) {
      if (LocationChanged != null) LocationChanged(this, EventArgs.Empty);
      if (IsAvailableAt(movement.Place) != IsAvailable) {
        if (AvailabilityChanged != null) AvailabilityChanged(this, EventArgs.Empty);
      }
      _current_location = movement.Place;
    }
  }

這個應用程式的畫面可能會選擇為所有顧問註冊這些事件,但我在這裡會使用事件聚合器事件聚合器會透過註冊所有事件來監控顧問。

class EventAggregator...

  public void Listen (Consultant subject) {
    subject.LocationChanged += new Consultant.ConsultantEventHandler(HandleConsultantLocationChanged);
    subject.AvailabilityChanged += new Consultant.ConsultantEventHandler(HandleConsultantAvailabilityChanged);
  }

當它收到一個事件時,它會發出兩個事件,一個是針對變更類型的特定事件,另一個是只表示已發生變更的概括事件。這允許客戶註冊他們希望的粒度。

class EventAggregator...

  private void HandleConsultantLocationChanged (Consultant consultant, EventArgs args) {
    if (ConsultantLocationChanged != null) ConsultantLocationChanged(consultant, args); 
    if (ConsultantChanged != null) ConsultantChanged(consultant, args);
  }
  private void HandleConsultantAvailabilityChanged (Consultant consultant, EventArgs args) {
    if (ConsultantAvailabilityChanged != null) ConsultantAvailabilityChanged(consultant, args);
    if (ConsultantChanged != null) ConsultantChanged(consultant, args);
  }
  public event Consultant.ConsultantEventHandler ConsultantChanged;
  public event Consultant.ConsultantEventHandler ConsultantLocationChanged;
  public event Consultant.ConsultantEventHandler ConsultantAvailabilityChanged;

在大部分時間,我建議只使用一個粗粒度的事件。然而,這提供了一個很好的範例,說明如何透過提供各種不同的反應來回應輸入事件,具體取決於客戶的需求。這也允許事件聚合器作為適配器以及外觀。