監聽者模式 是一種比較常見的設計模式。
在日常的開發中,我們所使用的 事件 就是一種符合 監聽者模式 的功能。
對 監聽者模式 還不太明白的同學可以通過 WinForm 開發來理解這一概念。
在 WinForm 模式下,事件的使用率是非常高的,窗體中的每一個 Controller 都提供了大量的事件,諸如 Click、DoubleClick、Load、Focus 等等。
為什么會這樣設計呢?
因為,當你編寫一個與業務無關 控件 的時候,你應當只編寫與 顯示 相關的代碼,以 Button 為例。
編寫一個 Button 關心的是如何畫出符合尺寸大小的按鈕,什么顏色,什么邊框,字的位置。至於按下這個按鈕需要執行什么,你在編寫 Button 還不知道,必須交給 外面 去處理。
所以使用 事件 將點擊的信號發送出去,交給外面去處理。
在我們編寫業務的時候會用到事件嗎?
很少有人會在業務代碼中使用 事件,一個常見的數據操作流程如下:
- 前台通過 Http 請求提交數據
- 通過 WebApi 框架內部的調度,執行某個 Controller 上的某個 Method
- 開發人員校驗提交數據的有效性。可能是通過直接在 Controller 中實現,也可能通過 AOP 等形式實現
- 將數據交由服務層處理
- 服務層經一定處理,將數據交由持久層處理
- 持久層將數據持久化
從流程上看,整個開發過程自始至終都是實現業務的過程,不像 Button 那樣,有業務相關的,有業務無關的,可以通過事件進行分離。
但事實上,業務與業務之間也是需要分離的。
舉個例子
當我們將系統中的一個用戶刪除時,大體需要做以下三件事
- 檢查是否可以刪除這個用戶。比如是否存在只能由此用戶才能處理的待辦事項等其它場景。
- 刪除用戶數據。這里可能是物理刪除、也可能是邏輯刪除。
- 刪除后操作。清除刪除了此用戶后,可能存在的 孤島數據。比如該用戶的個性化配置、頭像文件、個人網盤等等。
上述 3 個步驟中,只有 2. 適合在形如 UserService 中完成。
其它兩項並不適合,原因如下
- UserService 是一個可能極可能被其它 Service 依賴的接口,如果在此處依賴其它 Service 就會出現循環依賴的現象
- 當你在開發 UserService.Delete(User user) 時,其余的功能肯定是沒有被開發出來的。此時開發者,還沒有能力去實現這 1. 、2. 里的功能
- 難維護,你可以想象你要維護形如下面的代碼是不是就頭大。
public class UserService : IUserService
{
private readonly SomeService1 someService1;
private readonly SomeService2 someService2;
private readonly SomeService3 someService3;
public UserService(SomeService1 someService1,
SomeService2 someService2,
SomeService3 someService3)
{
this.someService1 = someService1;
this.someService2 = someService2;
this.someService3 = someService3;
// ...
// ...
// ...
}
public void Delete(User user)
{
someService1.CheckCanDeleteUser(user);
someService2.CheckCanDeleteUser(user);
someService3.CheckCanDeleteUser(user);
//...
//...
//...
// you can add more checker here
// but you should inject the component in the Ctor.
this.userRepo.Delete(user);
someService4.CleanUserData(user);
someService5.CleanUserData(user);
someService6.CleanUserData(user);
// ...
// ...
// ...
// you can add more cleaner here
// but you should inject the component in the Ctor.
}
}
形如上面的代碼很難維護,代碼行數也一定超出了人性化范疇。
更重要的,在一個真正的生產環境中,連上面的例子都做不到 :
- 不是每一個人在開發 SomeServiceX 的時候,都會留有一個 CheckCanDeleteUser 或 CleanUserData 的方法,名稱可能不太一樣,或者壓根就沒有,畢竟這不是它的業務邏輯范疇。
- 每個人在編寫自己范疇的 SomeServiceX 時,也不知道系統中 哪個數據 在 哪個操作 時需要自己來配合。如果每當有一個 需求 被提出都要去做一個的時候,那 SomeServiceX 也會變得非常臃腫,可能會比較像的樣子
public class SomeServiceZ : ISomeServiceZ
{
public void CheckCanDeleteUser(User user){}
public void CheckCanDeleteEvent(Event @event){}
public void CheckCanChangeEventDate(Event @event, DateTime newDate);
public void CheckCanDeleteOrder(Order order);
public void CheckCanModifyDeliveryDate(Order order, DateTime new DeliveryDate);
// ...
// ...
public void CleanOrderData(Order order);
public void CleanUserData(User user);
public void CleanEventData(Event @event);
//...
//...
}
很容易發現,最后這個 Service 不是在為自己 服務 ,而是在為系統中各種各樣的其它操作 服務 。
我們需要 事件
有了事件,我們只要在 UserService.Delete(User user) 內編寫兩個事件 Deleting 和 Deleted 即可。
剩下來的事只要交給監聽這些事件的類。
public class UserService : IUserService
{
public event EventHandler<UserDeletingEventArgs> Deleting;
public event EventHandler<UserDeletedEventArgs> Deleted;
public void Delete(User user)
{
this.Deleting?.Invoke(this, new UserDeltingEventArgs(user));
this.userRepo.Delete(user);
this.Deleted?.Invoke(this, new UserDeletedEventArgs(user));
}
}
當我們滿心歡喜的寫到這兒的時候,問題又來了。
我們 什么時候、在哪兒 監聽這個事件。
在一個使用了 IOC / DI 的 Cotnroller 中。UserService 的實例是通過 構造函數 得到的。
private readonly IUserService userService;
public UserController(IUserService userService)
{
this.userService = userService;
// 不知道誰監聽這個事件
// 如何把所有需要監聽這個事件的 Service 都注入進來
// 那問題依賴沒有得到改善
// this.userService.Deleting +=
}
由此看來 EventHandler 所提供的 事件 功能並不能很好的解決大系統中的這些問題。
事件總線
總線 一詞來源於 電腦硬件,電腦由很多的部件組成,他們之間有的着大量的信息交換。
當鼠標點下的時候,內存、硬盤、顯示器 都會產生變化。
很明顯,各種設備之間不可能兩兩相連來 監聽 這些 事件。
每個設備只要把自己產生的 信息 發到一個類似於 大水管 的 總線 里。
其它設備各取所需的獲取這些 信息 再處理自己的信息。
我們基於同樣的思想可以在我們的應用系統里實現這種功能。
Reface.AppStarter 中的事件總線
Reface.AppStarter 中提供了開箱即用的事件總線功能。
事件 的 發起者,與 事件 的 處理者 完全不需要了解對方是誰,甚至於不關心對方是否存在。
使用方法
1 定義一個事件類型
事件類型 是關聯 發起者 與 處理者 的 契約。
- 一個 處理者 只能處理一個 事件類型
- 一個 事件類型 可以有多個或沒有 處理者
在 Reface.AppStarter 中定義 事件類型 ,只需要讓它繼承於 Event 類型,它的構造函數要求提供事件發起方的實例。
public class MyEvent : Reface.EventBus.Event
{
public string Message { get; private set; }
public ConsoleStarted(object source, string message) : base(source)
{
this.Message = message;
}
}
除了 source ,你還定義更多的屬性,以便 事件處理者 可以得到更多的信息。
2 發起事件
IEventBus 是發起事件的工具。
它的實例已經被注冊到了 Reface.AppStarter 的 IOC / DI 容器中了。
凡是通過 IOC / DI 創建的組件,都會自己注入 IEventBus 實例。我們回到之前 UserService 的例子
[Component]
public class UserService : IUserService
{
private readonly IEventBus eventBus;
public UserService(IEventBus eventBus)
{
this.eventBus = eventBus;
}
public void Delete(User user)
{
this.eventBus.Publish(new UserDeletingEvent(this, user));
this.userRepo.Delete(user);
this.eventBus.Publish(new UserDeletedEvent(this, user));
}
}
在 事件發起者 這里,不需要關心都有誰需要監聽這個事件,只要 發布 事件即可。
事件總線會根據 事件處理者 所能處理的 事件類 進行分配。
3 監聽事件
事件的監聽是由 IEventListener<T> 完成的。泛型 T 就是事件類型。
該接口簡單易懂,只有一個方法需要實現,那就是監聽后要處理的內容。
注意 : 為了能夠讓 Reface.AppStarter 中的容器捕捉到你的 Listener ,你需要為其加上 [Listener] 特征。
[Listener]
public class CheckUserDeleteByEventModule : IEventListener<UsrDeletingEvent>
{
// 由於該組件依然是從 Reface.AppStarter 的容器中創建的,所以這里也可以通過構造函數注入接口的實例
public readonly IEventService eventService;
public CheckUserDeleteByEventModule(IEventService eventService)
{
this.eventService = eventService;
}
// 你可以按最小的業務功能拆分你的事件監聽器
// 比如刪除用戶
// 你不需要寫一事件監聽器去檢查系統中所有業務單元同否允許刪除除用戶
// 你可以按業務單元逐一實現這些檢查
// 對於不能刪除的,只要拋出異常即可
public void Handle(UsrDeletingEvent @event)
{
if(doSomeCheck(@event.User))
{
throw new CanNotDeleteDataException(typeof(@event.User), @event.User.Id, "your reason");
}
}
}
最后
使用 事件總線 能大幅度減少系統的耦合度。
當系統的復雜度不斷提升時,還可以使用 消息總線 。它們的基本原因是一樣的,只不過 消息總線 為了 分布式 和 高並發 做出了很多的優化。
但在 單體應用模式 下, Reface.AppStarter 所提供的 事件總線 功能是完全能夠滿足需求的。
相關鏈接