盧彥
Observer模式簡介為了讓更多的人能夠看明白本文,所以在此之前,我們先來瞭解一下Observer模式的基本概念。
模式名稱:Observer
結構圖:
意圖: 定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時, 所有依賴於它的對象都得到通知並被自動更新。
適用性: 當一個抽象模型有兩個方面, 其中一個方面依賴於另一方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和複用。 當對一個對象的改變需要同時改變其它對象, 而不知道具體有多少對象有待改變。 當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之, 你不希望這些對象是緊密耦合的。 應用執行個體在編寫多層應用程式時,我們通常將展示層和資料邏輯層分隔開,比如很常見的View/Document結構,這種設計方式的好處帶來的結果通常是使用多視圖同時表示單一資料來源,比如一個Web網站可以方便的同時擁有針對電腦的Html頁面和針對手機的WAP頁面。使用這種結構時,為了保持資料顯示的一致性,必須要求資料來源在資料發生改變時能及時的逐一通知每一個和它綁定的展示層進行更新。但是問題在於資料層本身並不知道到底有多少個不同的展示層正在反映著它的資料內容。因此需要設計一套有效機制來完成這個目標。模式實現我們先看看來自《設計模式迷你手冊》的常規的C#實現代碼。
Subject(抽象目標): 目標知道它的觀察者。可以有任意多個觀察者觀察同一個目標。
//實現代碼class Subject{ //由於不知道有多少個觀察者,所以建立了一個觀察者鏈表 private ArrayList list = new ArrayList(); private string strImportantSubjectData = "Initial"; public string ImportantSubjectData { get { return strImportantSubjectData; } set { strImportantSubjectData = value; } } public void Attach(Observer o) { list.Add(o); o.ObservedSubject = this; } public void Detach(Observer o) { } public void Notify() { //在資料發生改變後遍曆列表通知觀察者 foreach (Observer o in list) { o.Update(); } }}
Observer(抽象觀察者): 為那些在目標發生改變時需要獲得通知的對象定義一個更新介面。
abstract class Observer {//內建一個需要觀察的對象 protected Subject s;public Subject ObservedSubject { get { return s; } set { s = value; }} abstract public void Update();}
ConcreteSubject(實體目標,在這裡相當於資料邏輯層): 將有關狀態存入各ConcreteSubject對象。當它的狀態發生改變時,向它的各個觀察者發出通知。
//在這裡基本上什麼都沒有做,資料的擷取可以放到GetState()裡面class ConcreteSubject : Subject{ public void GetState() { } public void SetState() { } }
ConcreteObserver(實體觀察者,在這裡就相當於展示層): 維護一個指向ConcreteSubject的引用。儲存有關狀態,這些狀態應與目標的狀態保持一致。實現Observer的更新介面以使自身狀態與目標狀態保持一致。
class ConcreteObserver : Observer { private string observerName; public ConcreteObserver(string name) { observerName = name; } override public void Update() { //將資料顯示出來 Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, s.ImportantSubjectData); } }
主函數:
public class Client{ public static int Main(string[] args) { ConcreteSubject s = new ConcreteSubject(); ConcreteObserver o1 = new ConcreteObserver("first observer"); ConcreteObserver o2 = new ConcreteObserver("second observer"); //註冊觀察者 s.Attach(o1); s.Attach(o2); s. ImportantSubjectData = "This is important subject data"; s.Notify(); return 0; }}
模式分析Observer模式的優點是實現了展示層和資料邏輯層的分離,並定義了穩定的更新訊息傳遞機制,類別清晰,並抽象了更新介面,使得可以有各種各樣不同的展示層(觀察者)。但是其缺點是每個外觀對象必須繼承這個抽像出來的介面類,這樣就造成了一些不方便,比如有一個別人寫的外觀對象,並沒有繼承該抽象類別,或者介面不對,我們又希望不修改該類直接使用它。雖然可以再應用Adapter模式來一定程度上解決這個問題,但是會造成更加複雜煩瑣的設計,增加出錯幾率。C#作為一種先進的現代物件導向語言,不但吸收了許多語言的精華,並創造了一些非常有用的新特性。在學習了C#語言之後,我發現利用C#專屬的Delegate可以來較好的解決這個問題。改進後的Observer模式實現先定義一個Delegate:
delegate void UpdateDelegate(string SubjectData);
Subject(抽象目標):
class Subject{ private string strImportantSubjectData = "Initial"; //定義一個事件容器,代替前面的觀察者對象列表 public event UpdateDelegate UpdateHandle; public string ImportantSubjectData { get { return strImportantSubjectData; } set { strImportantSubjectData = value; } } public void Notify() { //發出事件 if(UpdateHandle != null) UpdateHandle(strImportantSubjectData); }}
Observer(抽象觀察者): 無,因為不需要抽象介面類了,所以可以省去抽象觀察者類。
ConcreteSubject(實體目標):
//沒有任何改變 class ConcreteSubject : Subject{ public void GetState() { } public void SetState() { } }
ConcreteObserver(實體觀察者):
//為了能更加清楚的說明問題,這裡定義了兩個實體觀察者,注意,它們之間並沒有任何關係class Observer1{ private string observerName; public Observer1(string name) { observerName = name; } public void Update1(string ImportantSubjectData) { Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, ImportantSubjectData); } }class Observer2{ private string observerName; public Observer2(string name) { observerName = name; } public void Update2(string ImportantSubjectData) { Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, ImportantSubjectData); } }
主函數:
public class Client{ public static int Main(string[] args) { ConcreteSubject s = new ConcreteSubject(); Observer1 o1 = new Observer1("first observer"); Observer2 o2 = new Observer2("second observer"); //向目標註冊對象兩個觀察者,請注意,這裡僅僅只是添加了兩個方法, //不需要關心方法從何而來,也不需要關心目標類如何去調用他們。 s.UpdateHandle += new UpdateDelegate(o1.Update1); s.UpdateHandle += new UpdateDelegate(o2.Update2); s.ImportantSubjectData = "This is important subject data"; s.Notify(); return 0; }}
在這段代碼裡,沒有看到鏈表,沒有看到遍曆操作,沒有看到更新的方法是如何被調用,甚至沒有看到那些被聯絡到一起的類的抽象類別和抽象介面,但是目標對象卻能將資料更新資訊逐一發送到每個觀察者對象,並且還能更加容易的增加新的不同的觀察者對象,根本不需要知道它從何處繼承而來,也不需要統一他們的介面調用方法。這一切都歸功於靈活強大的C#。完整的源碼以下是完整的源碼:
namespace Observer_DesignPattern{ using System; delegate void UpdateDelegate(string SubjectData); class Subject { private string strImportantSubjectData = "Initial"; public event UpdateDelegate UpdateHandle; public string ImportantSubjectData { get { return strImportantSubjectData; } set { strImportantSubjectData = value; } } public void Notify() { if(UpdateHandle != null) UpdateHandle(strImportantSubjectData); } } class ConcreteSubject : Subject { public void GetState() { } public void SetState() { } } class Observer1 { private string observerName; public Observer1(string name) { observerName = name; } public void Update1(string ImportantSubjectData) { Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, ImportantSubjectData); } } class Observer2 { private string observerName; public Observer2(string name) { observerName = name; } public void Update2(string ImportantSubjectData) { Console.WriteLine("In Observer {0}: data from subject = {1}", observerName, ImportantSubjectData); } } public class Client { public static int Main(string[] args) { ConcreteSubject s = new ConcreteSubject(); Observer1 o1 = new Observer1("first observer"); Observer2 o2 = new Observer2("second observer"); s.UpdateHandle += new UpdateDelegate(o1.Update1); s.UpdateHandle += new UpdateDelegate(o2.Update2); s.ImportantSubjectData = "This is important subject data"; s.Notify(); return 0; } }}