觀察者模式 => 我訂閱了一個東西 當他發生變化時 希望他可以通知我 = 執行我肚子的函式
所以可知這個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);
}
}
}