C#設計模式之訂閱發布模式

什么是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 EventHandler<MyEventArgs> OnChange = 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 EventHandler<MyEventArgs> OnChange = 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 EventHandler<MyEventArgs> OnChange = delegate { };

            public void Raise()
            {
                MyEventArgs eventArgs = new MyEventArgs(1);

                List<Exception> exceptions = new List<Exception>();

                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

posted @ 2020-05-21 21:09  HueiFeng  閱讀(...)  評論(...編輯  收藏
ag二分彩