[Design Pattern] 觀察者模式 Observer Pattern

觀察者模式 => 我訂閱了一個東西 當他發生變化時 希望他可以通知我 = 執行我肚子的函式

 

所以可知這個Pattern裡面一定有些東西

1. 有一個集合 => 存哪些人訂閱我 => 當我有變化時我才知道要通知誰

2. 訂閱者的是 '被通知的' => 粉絲肚子裡的函式被粉專呼叫

                                        => 當粉專有新貼文時 由粉專通知所有粉絲

3. 通知的方法是粉專來執行所有粉絲肚子裡的某一個函式

 

假設我們有一個 群組 Group、裡面有很多人 Member

Group 可以被 Member 加入、Group 負責通知所有 Member 有新貼文

Group.通知() => foreach 所有 Member.被通知()

 

主程式會長得像這樣

ConcreteGroup group = new ConcreteGroup();
group.Add(new ConcreteMember(group, "Tom"));
group.Add(new ConcreteMember(group, "Mary"));
group.Add(new ConcreteMember(group, "John"));

group.Message = "Hello";
group.NotifyMember(); //等於呼叫 Tom.BeNotified()、Mary.BeNotified()、John.BeNotified()

Console.Read();

小提示: 當我有一個父類別叫 Member,而繼承 Member 的子類別如果想不出好名子,通常大家就會叫 ConcreteMember

 

結果會像這樣

Group 最重要的程式碼片段為

public void NotifyMember()
{
    foreach (var member in members)
    {
        //Group 通知 Member = 去 Call Member 的函式
        member.BeNotified();
    }
}

 

實際上通知以後要發生什麼事 是寫在 Member = 訂閱者的類別中

public override void BeNotified()
{
    Console.WriteLine(_memberName + " 收到訊息:" + _Group.Message);
}

 

完整程式碼如下


using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1
{
    /// <summary>
    /// Group 相依於 Member 這個類別 => 因為 Member 被加進來而發生關係 => 加入類別
    /// => 怎麼通知 Member 寫在 Group 中 (因為Member 有父類別、所有人被通知的方式都一樣 (override BeNotified()) => 所以通知可以寫在 Group 中)
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            ConcreteGroup group = new ConcreteGroup();
            group.Add(new ConcreteMember(group, "Tom"));
            group.Add(new ConcreteMember(group, "Mary"));
            group.Add(new ConcreteMember(group, "John"));

            group.Message = "Hello";
            group.NotifyMember(); //等於呼叫 Tom.BeNotified()、Mary.BeNotified()、John.BeNotified()

            Console.Read();
        }
    }

    abstract class Group
    {
        //群組裡有很多人 (抽象依賴抽象 不應依賴細節 所以不會去 New ConcreteXXX )
        private List<Member> members = new List<Member>();

        //加入群組
        public void Add(Member obj)
        {
            members.Add(obj);
        }

        //廣播 => 一人講話 所有人都會收到訊息
        public void NotifyMember()
        {
            foreach (var member in members)
            {
                //Group 通知 Member = 去 Call Member 的函式
                member.BeNotified();
            }
        }
    }

    class ConcreteGroup : Group
    {
        //假設目前只有文字訊息通知 => 未來可能有影片、錄音、圖片
        private string _Message;

        public string Message
        {
            get { return _Message; }
            set { _Message = value; }
        }
    }

    abstract class Member
    {
        //群組裡的人是被通知的
        public abstract void BeNotified();
    }

    class ConcreteMember : Member
    {
        private ConcreteGroup _Group;
        private string _memberName;

        private string _message;

        public ConcreteMember(ConcreteGroup pGroup, string pMemberName)
        {
            _Group = pGroup;
            _memberName = pMemberName;
        }

        public override void BeNotified()
        {
            Console.WriteLine(_memberName + " 收到訊息:" + _Group.Message);
        }
    }
}

 

這樣就完成了一個訂閱者模式

但考慮以下問題

所以很明顯我們得做兩件事

1. 對訂閱者 = 粉絲 毫無限制 任何人都能訂閱

    => 不會有共同的介面或類別、限制得繼承了才能被我通知

2. 但這樣表示 粉絲 跟 粉專 之間的關係消失了

    => 原本用 List<Member> 來存所有的追蹤者 再 Foreach.BeNotified() 現在就不能這樣做了

    => 變成用 Delegate += Member.BeNotified() ......如果你不知道 Delegate是什麼 請看 [C#] 委派 Delegate 與 Lambda

 

所以我們換個新的例子

粉絲追蹤網紅

一樣先看主程式

class Program
{
    static void Main(string[] args)
    {
        //有一個網紅
        Celebrity beautyGirl = new Celebrity();

        //有很多人追蹤
        Follower fansA = new Follower(beautyGirl, "粉絲 A");
        Follower fansB = new Follower(beautyGirl, "粉絲 B");

        //粉絲要怎樣被通知寫在這裡 (比對前一個例子會寫在網紅的類別中)
        beautyGirl.NewPostNotify += new MyEventHandler(fansA.BeNotifiedByApp);
        beautyGirl.NewPostNotify += new MyEventHandler(fansB.BeNotifiedByApp);
        beautyGirl.NewPostNotify += new MyEventHandler(fansB.BeNotifiedByMail);

        //網紅發文
        beautyGirl.Message = "網紅A 發出一張美圖";

        //網紅來通知大家
        beautyGirl.NotifyFans();  //等於呼叫 fansA.BeNotifiedByApp()、fansB.BeNotifiedByApp、fansB.BeNotifiedByMail

        Console.Read();
    }
}

 

執行結果會像

來截錄一下重點

定義好網紅發文時要 (觸發 = 委派) 哪些 (事件 = 函式)

beautyGirl.NewPostNotify += new MyEventHandler(fansA.BeNotifiedByApp);
beautyGirl.NewPostNotify += new MyEventHandler(fansB.BeNotifiedByApp);
beautyGirl.NewPostNotify += new MyEventHandler(fansB.BeNotifiedByMail);

 

而當她真的發文時 就等於是去執行剛剛委派進來的三個事件

//網紅來通知大家
beautyGirl.NotifyFans();  //等於呼叫 fansA.BeNotifiedByApp()、fansB.BeNotifiedByApp、fansB.BeNotifiedByMail

public void NotifyFans()
{
    NewPostNotify();
}

 

所以我們就能來看完整程式碼了

using System;

namespace ConsoleApplication2
{
    /// <summary>
    /// Celebrity 相依於 Follower 的事件 (BeNotifiedByXXX) => 因為網紅的委派增加了粉絲的事件而發生關係 => 加入事件委派
    /// => 由粉絲決定怎麼訂閱網紅、不只一種訂閱方式 (粉絲沒有父類別 => 通知這件事有各種變化 => 需移到主程式中自己判斷)
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            //有一個網紅
            Celebrity beautyGirl = new Celebrity();

            //有很多人追蹤
            Follower fansA = new Follower(beautyGirl, "粉絲 A");
            Follower fansB = new Follower(beautyGirl, "粉絲 B");

            //粉絲要怎樣被通知寫在這裡 (比對前一個例子會寫在網紅的類別中)
            beautyGirl.NewPostNotify += new MyEventHandler(fansA.BeNotifiedByApp);
            beautyGirl.NewPostNotify += new MyEventHandler(fansB.BeNotifiedByApp);
            beautyGirl.NewPostNotify += new MyEventHandler(fansB.BeNotifiedByMail);

            //網紅發文
            beautyGirl.Message = "網紅A 發出一張美圖";

            //網紅來通知大家
            beautyGirl.NotifyFans();  //等於呼叫 fansA.BeNotifiedByApp()、fansB.BeNotifiedByApp、fansB.BeNotifiedByMail

            Console.Read();
        }
    }

    //----------------------------------------------------
    interface ICelebrity
    {
        string Message { get; set; }
        void NotifyFans();
    }

    //----------------------------------------------------
    delegate void MyEventHandler();

    /// <summary>
    /// 網紅
    /// </summary>
    class Celebrity : ICelebrity
    {
        //網紅發文時 所有粉絲都要收到通知 
        public MyEventHandler NewPostNotify;
        private string _Message;

        public void NotifyFans()
        {
            NewPostNotify();
        }

        public string Message
        {
            get { return _Message; }
            set { _Message = value; }
        }
    }

    /// <summary>
    /// 沒有父類別 => 不用 override 任何父類別的函式 => 我想怎做就怎做
    /// </summary>
    class Follower
    {
        private string _followersName;
        private ICelebrity _Celebrity;

        public Follower(ICelebrity celebrity, string followersName)
        {
            _Celebrity = celebrity;
            _followersName = followersName;
        }

        //準備拿來被網紅叫用的函式 => 粉絲等候被網紅通知 => 推撥通知
        public void BeNotifiedByApp()
        {
            Console.WriteLine(_followersName + " 收到 App 推播:" + _Celebrity.Message);
        }

        //準備拿來被網紅叫用的函式 => 粉絲等候被網紅通知 => 郵件通知
        public void BeNotifiedByMail()
        {
            Console.WriteLine(_followersName + " 收到 Mail 通知:" + _Celebrity.Message);
        }
    }
}