什麼是Pub-Sub
發佈訂閱是一種設計模式,它允許應用程序組件之間進行鬆散耦合。
其實訂閱發佈設計中主要是發佈者生成事件通道,用於在不瞭解任何訂閱者存在的情況下通知訂閱者。
當然委託EventHandlers和Event關鍵字在此事件處理機制中擔任著重要的角色。下面我們來看看如何使用它們。
Pub和Sub的使用
首先我們看一個簡單地訂閱發佈模式.
定義一個Action委託,無返回值.
namespace PubSubPattern { public class Pub { public Action OnChange { get; set; } public void Raise() { if (OnChange != null) { //Invoke OnChange Action OnChange(); } } } class Program { static void Main(string[] args) { var p = new Pub(); p.OnChange += () => Console.WriteLine("Sub 1"); p.OnChange += () => Console.WriteLine("Sub 2"); p.Raise(); Console.WriteLine("Press enter !"); Console.ReadLine(); } } }
如上代碼我們創建了一個發佈者,並且我們調用委託進行創建我們匿名方法來訂閱。由於委託提供了多播功能,因此我們可以OnChange屬性上使用+=.
雖然說我們看著如上代碼執行無誤,但是程序中仍然存在一些問題,如果使用=而不是+=,那麼OnChange屬性中將會刪除第一個訂閱者。
由於OnChange是公共屬性,因此該類的任何外部用戶都可以進行調用p.OnChange().
使用Event關鍵字的發佈訂閱
下面我們來看看使用event關鍵字後的代碼
public class Pub { public event Action OnChange = delegate { }; public void Raise() { OnChange(); } } class Program { static void Main(string[] args) { Pub p = new Pub(); p.OnChange += () => Console.WriteLine("Sub 1"); p.OnChange += () => Console.WriteLine("Sub 2"); p.Raise(); Console.WriteLine("Press enter !"); Console.ReadLine(); } }
通過如上代碼我們試著去解決我們第一處所說的問題,我們會發現使用event
關鍵字後可以保護我們OnChange免受不必要的訪問。它不允許使用=也就是說他不允許直接進行分配委託,因此我們現在可以避免使用=,從而避免應用程序不必要的麻煩。
可能大家也會發現OnChange初始化為空委託delegate{}
。這樣可以確保我們的OnChange永遠不會為空。因為當我們其他進行對他調用的時候我們可以在代碼中進行刪除對他的非空檢查.
使用EventHandlers的發佈訂閱
其實在訂閱發佈中,發佈者和訂閱者都不知道彼此的存在。有個EventHandler
,它被稱為消息代理或者說事件總線,發佈者和訂閱者都應該知道它,它接收所有傳入的消息並且將它們進行轉發.
因此呢,在如下片段中我們使用EventHandler而不是用Action.
public delegate void EventHandler( object sender, EventArgs e )
默認情況下,EventHandler將發送對象和一些事件參數作為參數。
public class MyEventArgs : EventArgs { public int Value { get; set; } public MyEventArgs(int value) { Value = value; } } public class Pub { public event EventHandlerOnChange = delegate { }; public void Raise() { OnChange(this, new MyEventArgs(1)); } } class Program { static void Main(string[] args) { Pub p = new Pub(); p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value); p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value); p.Raise(); Console.WriteLine("Press enter !"); Console.ReadLine(); } }
如上代碼中通過pub類使用通用的EventHandler,它觸發EventHandler OnChange時需要傳遞的事件參數類型,在上面代碼片段中為MyArgs
事件中的異常
我們繼續說一種情況.大家看如下代碼片段
public class MyEventArgs : EventArgs { public int Value { get; set; } public MyEventArgs(int value) { Value = value; } } public class Pub { public event EventHandlerOnChange = delegate { }; public void Raise() { OnChange(this, new MyEventArgs(1)); } } class Program { static void Main(string[] args) { Pub p = new Pub(); p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value); p.OnChange += (sender, e) => { throw new Exception(); }; p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value); p.Raise(); Console.WriteLine("Press enter !"); Console.ReadLine(); } }
運行如上代碼後,大家會發現第一個訂閱者已經執行成功了,第二個訂閱者引發了異常,而第三個訂閱者未被調用.這是一個很尷尬的事情.
如果說我們覺得如上的過程不是我們預期的,我們需要手動引發事件並處理異常,這時候我們可以使用Delegate基類中定義的GetInvoctionList
來幫助我們實現這些。
我們繼續看如下代碼
public class MyEventArgs : EventArgs { public int Value { get; set; } public MyEventArgs(int value) { Value = value; } } public class Pub { public event EventHandlerOnChange = delegate { }; public void Raise() { MyEventArgs eventArgs = new MyEventArgs(1); Listexceptions = new List(); foreach (Delegate handler in OnChange.GetInvocationList()) { try { handler.DynamicInvoke(this, eventArgs); } catch (Exception e) { exceptions.Add(e); } } if (exceptions.Any()) { throw new AggregateException(exceptions); } } } class Program { static void Main(string[] args) { Pub p = new Pub(); p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value); p.OnChange += (sender, e) => { throw new Exception(); }; p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value); p.Raise(); Console.WriteLine("Press enter !"); Console.ReadLine(); } }
Reference
https://github.com/hueifeng/DesignPatterns-Samples/tree/master/PubSubPattern
https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c
[火星人 ] 深入瞭解C#設計模式之訂閱發佈模式已經有250次圍觀