65.9K
CodeProject 正在变化。 阅读更多。
Home

事件聚合器模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (20投票s)

2014年9月10日

CPOL

5分钟阅读

viewsIcon

122935

downloadIcon

3120

创建和使用事件聚合器模式

引言

本文深入探讨了事件聚合器模式,该模式用于企业应用程序软件。模式名称本身就说明了它处理我们软件应用程序中的事件。该模式也可以用于普通应用程序,而不必局限于企业应用程序。

为了理解文章提供的示例程序,我假设读者熟悉 .Net 反射线程同步概念。

背景

软件应用程序中的事件表示重要事情的发生,例如对象状态、组件状态、应用程序状态等的更改。从小型到大型的所有软件应用程序都使用事件来指示其发生的更改,并成为其内部和外部通信的支柱。

事件允许想要通过允许发送方对象[发布者]指定更改时发生的事件,然后允许多个接收方对象[订阅者]订阅它来通信的两个或多个对象之间进行松耦合。发布者对象不直接引用订阅者对象,也不知道它们的存在,并且只要与事件相关的接口不发生更改,就可以在不影响订阅者的情况下进行更改。

.NET 或 JAVA 等所有软件开发框架都提供了内置的事件处理机制来创建事件、指示事件发生、允许其他对象或组件注册和取消注册事件发生。几乎所有用户界面 (UI) 框架都严重依赖事件处理机制来开发与用户交互的 UI 对象。

以下部分描述了传统的事件处理及其在软件应用程序扩展时的缺点。

传统事件处理

以下步骤描述了传统的事件处理。

  1. 发布者会发布其事件并指定何时会调用它们。
  2. 订阅者然后会向发布者的事件注册自己。
  3. 随着发布者对象在其生命周期中发生变化,它会调用事件,通知订阅者。

传统事件处理的缺点

  • 如果存在多个订阅者和发布者,则代码将难以阅读和调试。
  • 事件的订阅者需要知道事件的发布者并通过事件名称直接引用它,从而导致它们之间紧密耦合。
  • 紧密耦合将不允许订阅者和发布者独立于彼此进行更改。
  • 订阅者负责注册和取消注册事件,在许多实际场景中,订阅者通常会忘记取消注册,导致订阅者和发布者都停留在内存中,造成内存泄漏。

事件聚合器模式

事件聚合器模式试图通过提供一个中心位置来发布和订阅事件来克服传统事件处理方法的限制,这个中心位置就是事件聚合器。事件聚合器负责事件的注册、注销和调用,从而松耦合发布者和订阅者。所有事件的发布者和订阅者都只会知道事件聚合器。下图显示了应用程序中典型的事件聚合器。

模式意图

  • 简化事件的订阅和退订
  • 将发布者与订阅者解耦,允许两者在不影响对方的情况下进行更改。
  • 减少系统资源的浪费,但可能取决于开发框架或环境的支持。
  • 便于添加新事件。
  • 事件的集中处理。

使用代码

实现事件聚合器模式有很多方法,具体的实现方式可能因您软件开发环境使用的框架和语言基础设施而异。我使用的示例程序是使用 C# 语言在 .NET 4.0 框架中开发的,我大量依赖反射和泛型来实现我的事件聚合器。

要使用示例程序中提供的事件聚合器,请按照以下步骤操作。

步骤 1:识别应用程序中的事件及其参数

在示例程序中,我有三个与 item 对象相关的事件以及它们的参数,如下所示。

 //1. Item created     
 public class ItemCreated     
 {         
     public Item Item { get; set; }     
 } 

 //2. Item Saved
 public class ItemSaved
 {
     public Item Item { get; set; }
 } 

 //3. Item Selected
 public class ItemSelected
 {
    public Item Item { get; set; }
 }

步骤 2:创建事件聚合器的单个全局实例

为了允许订阅和发布事件,请在应用程序中创建事件聚合器的单个全局实例,如下所示。

this.ea = new EventAggregator.EventAggregator();

此全局实例必须提供给应用程序中的所有发布者和订阅者。

步骤 3:订阅者订阅事件

要订阅事件,订阅者需要实现 ISubscriber<TEventType> 接口,其中 TEventType 是它感兴趣的事件的类型,这些事件在步骤 1 中已识别。接口定义如下

 public interface ISubscriber<TEventType>
    {
        void OnEventHandler(TEventType e);
    }

在示例程序中,ItemView 用户控件订阅了所有三个事件,如下所示。

public partial class ItemView : UserControl
                    , ISubscriber<ItemSaved>, ISubscriber<ItemSelected>, ISubscriber<ItemCreated>
{

   public ItemView(IEventAggregator ea)
   {
      InitializeComponent();
   
      //Subcribe to all event.
      ea.SubsribeEvent(this);
   }

  #region ISubscriber<ItemSelected> Members

  public void OnEventHandler(ItemSelected e)
  {

    this.Label.Content = string.Format("Item Selected {0}", e.Item.ItemNumber);

  }

  #endregion

  #region ISubscriber<ItemSaved> Members

   public void OnEventHandler(ItemSaved e)
   {

     this.Label.Content = string.Format("Item Saved {0}", e.Item.ItemNumber);

   }

  #endregion

  #region ISubscriber<ItemSaved> Members

  public void OnEventHandler(ItemCreated e)
  {

     this.Label.Content = string.Format("Item Created {0}", e.Item.ItemNumber);
 
  }

   #endregion

}

步骤 4:发布者发布事件

要发布事件,发布者需要调用 IEventAggregator.PublishEvent<TEventType>(TEventType eventToPublish) 方法,并传递在步骤 1 中识别的事件类型的实例。

在示例程序中,ItemListView 用户控件发布了 ItemSaved 事件,如下所示。

 this.EventAggregator.PublishEvent(new ItemSaved() { Item = savedItem });

事件聚合器内部窥探

事件聚合器实现 IEventAggregator 接口。接口定义如下

 public interface IEventAggregator
 {                
    void PublishEvent<TEventType>(TEventType eventToPublish);

    void SubsribeEvent(Object subscriber);
 }

事件聚合器为每个事件类型维护一个内部字典,其中包含其调用列表,作为 WeakReference。

当订阅者调用 SubsribeEvent(Object subscriber) 方法时,它使用反射来确定该实例支持的所有 ISubscriber<TEventType> 类型,并将其保存在字典中。该方法应该只为订阅者调用一次,通常在其构造函数逻辑中。

当发布者调用 PublishEvent<TEventType>(TEventType eventToPublish) 方法时,它会获取已订阅 TEventType 的实例的调用列表,并在它们仍然在应用程序中活动时调用它们,否则会将它们从调用列表中删除。

关注点

当向 .NET 开发人员展示事件聚合器代码时,他们有时会混淆 .NET 应用程序中通常使用的传统事件和委托方法。

© . All rights reserved.