用例:假設公司發佈了一個公告 須要經過短信 和 郵件分別2種方式 通知員工
安全
1:首先咱們創建領域模型微信
/// <summary> /// 領域核心基類 /// </summary> public abstract class Core { public string Id { set; get; } = Guid.NewGuid().ToString(); } public interface ICore { }
2:消息模型ide
/// <summary> /// 通知的領域模型 /// </summary> public class Notice : Core { /// <summary> /// 通知內容 /// </summary> public string Message { set; get; } /// <summary> /// 通知發送時間 /// </summary> public DateTime DateTime { set; get; } = DateTime.Now; }
這個時候咱們會想到 創建2個服務類 一個是SmsService 和 EmailService服務 分別用來發送短信和Emailui
public class EmailService { public EmailService() { } public EmailService(Entity.Notice notice) => Console.WriteLine($"郵件通知:{notice.Message} 發送時間:{notice.DateTime}"); }
public class SmsService { public SmsService() { } public SmsService(Entity.Notice notice) => Console.WriteLine($"短信通知:{notice.Message} 發送時間:{notice.DateTime}"); }
static void Main(string[] args) { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); Entity.Notice notice = new Entity.Notice() { Message ="明天加班 加班 加班 ~!!! 重要的事情說三遍" }; new Service.EmailService(notice); new Service.SmsService(notice); }
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance)
這裏主要是由於.NET CORE中文輸出會致使亂碼 須要加上編碼配置。也能夠換上非Core平臺
看看運行後的效果
運行後的效果彷佛已經知足了咱們的需求 公司公告分別以2種方式發送出去了
這樣就帶來了一個問題 若是將來社交發展須要多平臺發送通知呢。。假設這裏有還有QQ 默默 探探 釘釘 微信 等等
這一系列的消息推送方式 那咱們的代碼裏是否是這樣 ?
Entity.Notice notice = new Entity.Notice() { Message ="明天加班 加班 加班 ~!!! 重要的事情說三遍" }; new Service.EmailService(notice); new Service.SmsService(notice); new Service.QQService(notice); new Service.WeiXinService(notice); new Service.MomoService(notice); new Service.DingDService(notice); new Service.TanTanService(notice);
這樣寫顯然看起來是一個很是蛋疼的事。仔細想一想這一系列的消息推送不變的是 事件消息源 也就是Notice對象 this
而且全部發送消息的方法都是被動的接收這個對象 , 這樣咱們就能夠把2者的關係理解成 消息源是發佈者,編碼
具體處理消息發送的是訂閱者,從而咱們換一個思路去改造以前的方法spa
public interface IEventHandler<T> where T : Entity.Core { /// <summary> /// 訂閱對象的具體實現 /// </summary> /// <param name="entity"></param> void Handler(T entity); }
定義一個泛型接口,全部訂閱者必須實現這個接口.net
/// <summary> /// Email形式處理 /// </summary> public class EmailEventHandler : IEventHandler<Entity.Notice> { public void Handler(Notice notice) { Console.WriteLine($"郵件通知:{notice.Message} 發送時間:{notice.DateTime}"); } }
/// <summary> /// 短信形式處理 /// </summary> public class SmsEventHandler : IEventHandler<Entity.Notice> { public void Handler(Notice notice) { Console.WriteLine($"短信通知:{notice.Message} 發送時間:{notice.DateTime}"); } }
這是改造的第一步,改造的目的是讓它能以一種自動的方式處理,而不像以前同樣須要一個一個對象的new出來,線程
作到能像看電視同樣,只要你打開電視就能收看到傳輸過來的畫面code
定義一個消息總線的接口 用來管理 訂閱者的註冊 以及消息的發佈
public interface IBus { /// <summary> /// 默認訂閱全部事件消息 啓動時調用 /// </summary> void SubscribeAll(); /// <summary> /// 訂閱 /// </summary> /// <param name="type"></param> /// <param name="data"></param> void Subscribe(Type type, object data); /// <summary> /// 發佈訂閱 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="eneity"></param> void Publish<T>(T eneity) where T : Entity.Core; }
定義一個BusManager來實現 IBus
private BusManager() { if (_instance == null) Bus = new BusService(); } private static object _lock = new object(); private static BusManager _instance; public static IBus Instance { get { if (_instance == null) { lock (_lock) { if (_instance == null) _instance = new BusManager(); } } return _instance; } }
/// <summary> /// 主要是針對 事件源和具體的事件處理註冊關係。ConcurrentDictionary保證了線程的安全 /// </summary> private static ConcurrentDictionary<Type, List<object>> _dicHandlers = new ConcurrentDictionary<Type, List<object>>();
/// <summary> /// 判斷type是不是abs的實現類或者子類/ /// .net Core中對反射進行了單獨的處理 反射後一些詳細屬性都要經過GetTypeInfo獲取 原對象只保留了一些基本屬性 /// </summary> /// <param name="type"></param> /// <param name="abs"></param> /// <returns></returns> private bool IsAssignableFrom(Type type, Type abs) { /// if ((abs.GetTypeInfo().IsAbstract || abs.GetTypeInfo().IsInterface) && abs.IsAssignableFrom(type)) return true; else { if (type.GetInterfaces().Any(o => o.GetTypeInfo().IsGenericType && o.GetGenericTypeDefinition() == abs)) return true; } return false; } /// <summary> /// 判斷2個類型是否相同 /// </summary> private Func<object, object, bool> _Equals = (o1, o2) => { return o1.GetType() == o2.GetType(); };
上述方法都是爲了實現IBus接口所作的鋪墊 若有.Net Core反射疑問的自行Bing
public void Subscribe(Type type, object data) { lock (_lock) { if (_dicHandlers.ContainsKey(type)) { var _handlers = _dicHandlers[type]; if (!_handlers.Any(o => _Equals(o, data))) _handlers.Add(data); } else { _dicHandlers[type] = new List<object>() { data }; } } }
實現的單個對象的加載 Key爲具體的發佈對象類型, Value是具體的訂閱者的行爲實現集合,這裏是多個訂閱者的實現
ConcurrentDictionary 裏的關係
/// <summary> /// 初始化 默認的全部實現都訂閱事件 /// </summary> public void SubscribeAll() { ///加載程序集,具體的你也能夠加載項目路徑下全部的dll或者exe var assembly = Assembly.Load(new AssemblyName("ConsoleApp2")); assembly.GetTypes().Where(x => x.GetTypeInfo().IsClass && !x.GetTypeInfo().IsAbstract && !x.GetTypeInfo().IsInterface).ToList().ForEach(x => { if (IsAssignableFrom(x, typeof(IEventHandler<>))) { ///反射建立對象 var entity = Activator.CreateInstance(x); ///得到到泛型參數的類型 var key = x.GetInterfaces().FirstOrDefault().GetGenericArguments().FirstOrDefault(); this.Subscribe(key, entity); } }); }
事件源的發佈
/// <summary> /// 事件源的發佈 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="eneity"></param> public void Publish<T>(T eneity) where T : Core { var type = eneity.GetType(); if (_dicHandlers.ContainsKey(type) && _dicHandlers[type] != null) { _dicHandlers[type].ForEach(o => { var eve = o as IEventHandler<T>; eve.Handler(eneity); }); } }
上面的全部代碼基本上就完成了一個簡單的事件總線驅動的模式,
試試最終的運行效果。
是否是感受很神奇,這就是單純本身理解的事件總線模式。