摘要:[Design Pattern] 觀察者模式(Observer Pattern)
前言
上一篇的 策略模式(Strategy Pattern) 把跑車行為裝箱吧 是講設計模式的第一篇,接下來繼續說明第二種設計模式 觀察者模式(Observer Pattern) ,觀察者模式是什麼呢? 舉個最常見也常用的例子來說明,相信大家應該都訂過報紙,那報紙怎麼來的? 當然是由報社產生出來的,那你會平白無故就收到報紙嗎? 當然不會啦,是需要自行去報社說想訂閱報紙對吧? 那當我不想看這家報紙了呢? 我就要自行去跟報社說下個月開始我不定了!
理解上述的流程後來整理一下,當我有訂閱報紙的時候報社每天都會將最新的新聞主動發送給我知道,所有的新聞資訊都是統一由報社整理後發送給每個有訂閱報紙的客戶,客戶是被動的接收到報社的報紙才知道發生了什麼事,在這我把報社改名為主題而客戶改名為觀察者,而 主題 + 觀察者 = 觀察者模式。
對於觀察者模式可以用一句話描述,於 Head First Design Patterns 中提到「觀察者模式定義了物件之間的一對多關係,如此一來,當一個物件改變狀態,其他相依者都會收到通知並自動被更新」,觀察者模式基本類別圖如下:
定義了 Subject 介面要求實作的類別需要實踐註冊、移除、通知方法,而定義Observer介面要求實作的類別必須要實踐更新方法,實作Observer介面的類別可以很多個,但是通通只會指到一個實作Subject介面的類別,當有新的觀察者想要加入此主題時就會去呼叫 registerObserver() 方法加入監聽名單,而當有舊的觀察者想退出這個主題時就會呼叫 removeObserver() 將自己本身從監聽名單中移除,最後如果Subject有新消息要通知有加入監聽名單的Observer就會呼叫 notifyObservers() 方法去依序執行所有Observer的 Update() 方法,透過介面的將主題與觀察者兩者鬆綁了。
範例
接下來就用個範例來實際看一下該如何撰寫觀察者模式的程式碼,延續前言的範例我將產生一個報社與訂閱的客戶,初步規劃的類別圖如下:
NewspaperOffice類別將實作ISubject介面,在該類別中再增加了三個方法分別是訂閱報紙、取消訂閱報紙、發送報紙,這是為了包裝ISubject介面需要實踐的三個方法而用,在Customer類別將實作IObserver介面並實踐Update()方法,其中的相依關係只透過IObserver介面做串連。
實際看一下程式碼:
public interface ISubject
{
void RegisterObserver(IObserver pObserver);
void RemoveObserver(IObserver pObserver);
void notifyObservers(string pContent);
}
public interface IObserver
{
void Update(string pMessage);
}
public class NewspaperOffice : ISubject
{
List lstObservers; // 使用List來存放觀察者名單
public NewspaperOffice()
{
lstObservers = new List();
}
// 加入觀察者
public void RegisterObserver(IObserver pObserver)
{
lstObservers.Add(pObserver);
}
// 移除觀察者
public void RemoveObserver(IObserver pObserver)
{
if (lstObservers.IndexOf(pObserver) >= 0)
lstObservers.Remove(pObserver);
}
// 發送通知給在監聽名單中的觀察者
public void notifyObservers(string pContent)
{
foreach (IObserver observer in lstObservers)
{
observer.Update(pContent);
}
}
// 訂閱報紙
public void SubscribeNewspaper(IObserver pCustomer)
{
RegisterObserver(pCustomer);
}
// 取消訂閱報紙
public void UnsubscribeNewspaper(IObserver pCustomer)
{
RemoveObserver(pCustomer);
}
// 發送新消息
public void SendNewspaper(string pContent)
{
Console.WriteLine("Send News..");
notifyObservers(pContent);
}
}
在NewspaperOffice類別中使用了List來記錄目前加入的觀察者有哪些且List也包含Add()跟Remove()方法可以直接使用。
public class Customer : IObserver
{
public string MyName { private get; set; }
public Customer(string pName)
{
MyName = pName;
}
// 更新最新消息
public void Update(string pMessage)
{
Console.WriteLine(" {0} receive a new message:{1}", MyName, pMessage);
}
}
在Customer類別中實作了Update()方法,往回看到NewspaperOffice類別中的notifyObservers()方法,notifyObservers()方法在做的動作就是透過IObserver介面我們知道了有Update()方法可以呼叫,所以在這就是依序的主動呼叫觀察者的Update()方法,而每個觀察者在Update()方法被呼叫時就可以去執行自己本身的相關邏輯。
最後來看一下執行的部分:
static void Main(string[] args)
{
// 產生一間報社
NewspaperOffice office = new NewspaperOffice();
// Arvin 想要訂閱報紙
Customer arvin = new Customer("Arvin");
office.SubscribeNewspaper(arvin);
// Jack 想要訂閱報紙
Customer jack = new Customer("Jack");
office.SubscribeNewspaper(jack);
// 報社發送了第一則新聞
office.SendNewspaper("News One.......");
// Arvin 不想看報紙了,要退訂
office.UnsubscribeNewspaper(arvin);
// 報社發送了第二則新聞
office.SendNewspaper("News Two.......");
Console.Read();
}
這邊應該可以很清楚的看到,當新的客戶訂閱報紙時呼叫SubscribeNewspaper()方法就會被加入到受監聽的名單中反之退訂的時候也是一樣,而當報社要發布新新聞的時候將呼叫SendNewspaper()方法傳送新的資訊給監聽者名單中的客戶,如此就是觀察者模式的應用,主要目標就是透過使用觀察者模式將物件之間的緊密度做鬆綁。
範例程式碼
參考資料
Head First Design Pattern 深入淺出設計模式
以上文章敘述如有錯誤及觀念不正確,請不吝嗇指教
如有侵權內容也請您與我反應~謝謝您 :)