在C#中,我們可以在一個類中定義自己的事件,而其他的類可以訂閱該事件,當某些事情發生時,可以通知到該類。這對于桌面應用或者獨立的windows服務來說是非常有用的。但對于一個web應用來說是有點問題的,因為對象都是在web請求中建立的,而且這些對象生命周期都很短,因而注冊某些類的事件是很困難的。此外,注冊其他類的事件會使得類緊耦合。事件總線便可以用來解耦并重複利用應用中的邏輯。
一、說明
是以今天介紹一個簡單的事件總線,它是事件釋出訂閱模式的實作,讓我們能在領域驅動設計(DDD)中以事件的弱引用本質對我們的子產品和領域邊界很好的解耦設計。
目前,所有的源代碼已經送出到github 上,位址:
https://github.com/weizhong1988/Weiz.EventBus程式目錄結構如下:

二、事件總線帶來的好處和引入的問題
好處比較明顯,就是獨立出一個
釋出訂閱子產品
,調用者可以通過使用這個子產品,屏蔽一些線程切換問題,簡單地實作釋出訂閱功能。
壞處可能比較隐晦,但這些需要足夠引起我們的重視
- 大量的濫用,将導緻邏輯的分散,出現問題後很難定位。
- 沒辦法實作強類型,在編譯的時候就發現問題。
- 代碼可讀性有些問題,IDE無法識别這些協定,對IDE不友好。
總得來說,如果項目裡面有大量的事件互動,那麼還是可以通過
EventBus
來實作,否則還是推薦自己在子產品内部實作
觀察者模式。
三、Weiz.EventBus介紹
Weiz.EventBus事件總線是被所有觸發并處理事件的其他類共享的單例對象。要使用事件總線,首先應該獲得它的一個引用。下面有兩種方法來處理:
1、訂閱事件
觸發事件之前,應該先要定義該事件。EventBus為我們提供了Subscribe 方法來訂閱事件:
public void Subscribe<TEvent>(IEventHandler<TEvent> eventHandler) where TEvent : IEvent
{
//同步鎖
lock (_syncObject)
{
//擷取領域模型的類型
var eventType = typeof(TEvent);
//如果此領域類型在事件總線中已注冊過
if (_dicEventHandler.ContainsKey(eventType))
{
var handlers = _dicEventHandler[eventType];
if (handlers != null)
{
handlers.Add(eventHandler);
}
else
{
handlers = new List<object>
{
eventHandler
};
}
}
else
{
_dicEventHandler.Add(eventType, new List<object> { eventHandler });
}
}
}
所有的事件都內建自IEvent,該類包含了類處理事件需要的屬性。
調用方式如下:
var sendEmailHandler = new UserAddedEventHandlerSendEmail();
var sendMessageHandler = new UserAddedEventHandlerSendMessage();
var sendRedbagsHandler = new UserAddedEventHandlerSendRedbags();
Weiz.EventBus.Core.EventBus.Instance.Subscribe(sendEmailHandler);
Weiz.EventBus.Core.EventBus.Instance.Subscribe(sendMessageHandler);
//Weiz.EventBus.Core.EventBus.Instance.Subscribe<UserGeneratorEvent>(sendRedbagsHandler);
Weiz.EventBus.Core.EventBus.Instance.Subscribe<OrderGeneratorEvent>(sendRedbagsHandler);
2、釋出事件
對于事件源,則可以通過Publish 方法釋出事件。觸發一個事件很簡單,如下所示:
public void Publish<TEvent>(TEvent tEvent, Action<TEvent, bool, Exception> callback) where TEvent : IEvent
{
var eventType = typeof(TEvent);
if (_dicEventHandler.ContainsKey(eventType) && _dicEventHandler[eventType] != null &&
_dicEventHandler[eventType].Count > 0)
{
var handlers = _dicEventHandler[eventType];
try
{
foreach (var handler in handlers)
{
var eventHandler = handler as IEventHandler<TEvent>;
eventHandler.Handle(tEvent);
callback(tEvent, true, null);
}
}
catch (Exception ex)
{
callback(tEvent, false, ex);
}
}
else
{
callback(tEvent, false, null);
}
}
下面是釋出事件的調用:
var orderGeneratorEvent = new OrderGeneratorEvent { OrderId = Guid.NewGuid() };
System.Console.WriteLine("{0}下單成功", orderGeneratorEvent.OrderId);
Weiz.EventBus.Core.EventBus.Instance.Publish(orderGeneratorEvent, CallBack);
釋出訂單下單成功通知消息。
四、定義處理事件
1、定義處理事件
要處理一個事件,應該要實作IEventHandler接口,如下所示:
/// <summary>
/// send email
/// </summary>
public class UserAddedEventHandlerSendEmail : IEventHandler<UserGeneratorEvent>
{
public void Handle(UserGeneratorEvent tEvent)
{
System.Console.WriteLine(string.Format("{0}的郵件已發送", tEvent.UserId));
}
}
這裡定義了一個使用者注冊成功後,發送郵件通知事件。
2、處理多事件
在一個單一的處理句柄中,可以處理多個事件。這時,你應該為每個事件實作IEventHandler。比如:
/// <summary>
/// red bags.
/// </summary>
public class UserAddedEventHandlerSendRedbags : IEventHandler<UserGeneratorEvent>,IEventHandler<OrderGeneratorEvent>
{
public void Handle(OrderGeneratorEvent tEvent)
{
System.Console.WriteLine(string.Format("{0}的下單紅包已發送", tEvent.OrderId));
}
public void Handle(UserGeneratorEvent tEvent)
{
System.Console.WriteLine(string.Format("{0}的注冊紅包已發送", tEvent.UserId));
}
}
這裡定義了多個時間,包括下單發送紅包事件和人員注冊發送紅包事件。
3、運作驗證
接下來運作上面的示例程式,檢視事件訂閱的情況。
我們看到人員注冊成功後,成功觸發了郵件消息事件、短信消息事件、紅包消息事件。 下單成功後,自動觸發紅包事件。
最後
以上,就把事件總線介紹完了,完整的代碼,請到github 上下載下傳,這個隻是EventBus 的簡單實作,各位可以根據自己的實際場景和需求,優化修改。