一、意圖
定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時, 所有依賴於它的對象都得到通知並被自動更新。
二、適用性
《設計模式》中提到在以下任一情況下可以使用觀察者模式:
1.當一個抽象模型有兩個方面, 其中一個方面依賴於另一方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和複用。
2.當對一個對象的改變需要同時改變其它對象, 而不知道具體有多少對象有待改變。
3.當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之, 你不希望這些對象是緊密耦合的。
三、組成
——抽象主題(Subject)角色
把所有對觀察者對象的引用儲存在一個集合中,每個抽象主題角色都可以有任意數量的觀察者。抽象主題提供一個介面,可以增加和刪除觀察者。一般用介面或抽象類別來實現抽象主題角色。
——抽象觀察者(Observer)角色
為具體的觀察者定義一個更新介面,在得到主題的通知時更新自己。
——具體主題(Concrete Subject)角色
在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。是抽象主題的子類(或實現)。
——具體觀察者(Concrete Observer)角色
該角色實現抽象觀察者角色所要求的更新介面,以便本身的狀態與主題的狀態相協調。如果需要,具體觀察者角色可以儲存一個指向具體主題角色的引用。
四、結構
上述的Subject和Observer可以用java的interface或abstract class實現,在設計模式中“介面”並不一定指的就是java(或其他語言)中的interface。
有時我們或許並不需要在主題或觀察者中加入記錄本身狀態的State屬性,在觀察者中或許也不需要儲存一個Subject類型的指向主題的引用。觀察者模式的重點在於:使用介面或抽象類別實現具體主題與具體觀察者的松耦合、主題通過維持Observer類型的集合與觀察者實現一對多依賴、主題增加刪除某觀察者、主題改變時通知更新其所有的觀察者。
五、實現
1.最簡單的實現
本簡單實現沒有在主題和觀察者中加入記錄本身狀態的屬性,並且在觀察者中也沒有儲存一個指向主題的引用,只是實現了觀察者模式的基本特點,可以根據自身的需求對其擴充(比如:加入狀態或指向主題的引用等)。
(1)抽象主題角色
public interface Subject{public void registerObserver(Observer o);public void removeObserver(Observer o);public void notifyObservers();}
此處使用介面而不是用抽象類別實現抽象主題,是因為java(或C#等)是單繼承的,當我們使用抽象類別實現抽象主題時,那麼某類想具有Subject的行為的同時又想具有另一超類的行為時,就會陷入兩難的境地。
(2)具體主題角色
public class ConcreteSubject implements Subject{List<Observer> observers = new ArrayList<Observer>();@Overridepublic void registerObserver(Observer o){if (o == null)throw new NullPointerException();if (!observers.contains(o)){observers.add(o);}}@Overridepublic void removeObserver(Observer o){observers.remove(o);}@Overridepublic void notifyObservers(){for (Observer o : observers)o.update();}}
(3)抽象觀察者角色
public interface Observer{public void update();}
(4)具體觀察者角色
public class ConcreteObserver implements Observer{@Overridepublic void update(){ //加入hashCode以區別不同的對象 System.out.println(this.hashCode()+" says: I'm notified !");}}
(5)用戶端測試:
public class Client{public static void main(String[] args){Subject subject = new ConcreteSubject();Observer o1 = new ConcreteObserver();Observer o2 = new ConcreteObserver();Observer o3 = new ConcreteObserver();subject.registerObserver(o1);subject.registerObserver(o2);subject.registerObserver(o3);subject.registerObserver(o1);//測試重複註冊subject.notifyObservers();}}
2.增強:加入主題狀態,並在觀察者中儲存對主題的引用等
(1).抽象主題角色
public abstract class Subject{private List<Observer> observers = new ArrayList<Observer>();public void registerObserver(Observer o){if (o == null)throw new NullPointerException();// 避免同一個觀察者註冊多次if (!observers.contains(o)){observers.add(o);}}public void removeObserver(Observer o){observers.remove(o);}public void notifyObservers(){for (Observer o : observers){o.update();}}}
這裡使用的是抽象類別實現的抽象主題角色,因為registerObserver、removeObserver、notifyObservers是所有子類公用的部分,將它們實現在超類中是理所當然的,可以複用父類的代碼,子類可以更簡潔地實現具體主題。但對於java這種單繼承的語言,會出現我們前面所說的兩難境地。
(2).抽象觀察者角色
public interface Observer{public void update();}
依然使用介面實現抽象觀察者角色,主題與觀察者的松耦合的就是體現在這裡,對於java等單繼承的語言,實現抽象觀察者時,使用介面優於使用抽象類別。
(3).具體主題角色
public class ConcreteSubject extends Subject{Object state;//具體主題本身的狀態public Object getState(){return state;}public void setState(Object state){this.state = state;}}
(4)具體觀察者角色
public class ConcreteObserver implements Observer{// 具體觀察者內部維持一個ConcreteSubject類型的指向具體主題的引用ConcreteSubject subject;Object state;public ConcreteObserver(ConcreteSubject subject){ if(subject==null)//觀察者不能監聽null throw new NullPointerException(); this.subject = subject;this.subject.registerObserver(this);} //解除對主題的依賴(註冊)public void unRegister(){this.subject.removeObserver(this);}@Overridepublic void update(){state = this.subject.getState();// hashCode用於區別不同的觀察者System.out.println(this.hashCode() + "I'm notified!!");}}
此處具體觀察者中包含一個主題類型的引用,注意是ConcreteSubject類型的,我們希望它是Subject類型的,因為這樣我們就可以使用多態讓具體觀察者先解除對原主題的註冊(因為它內部只維持了一個主題引用而不是多個)再註冊到不同類型的具體主題。但是在這裡,很難辦到,因為State狀態資訊是具體主題所有的,不同的具體主題有不同的狀態資訊。這樣的設計符合以上結構圖中的描述,在《設計模式》這本經典書籍中的結構圖也是這樣描述的,我想,觀察者模式的松耦合體現在:所有觀察者都實現了Observer介面使主題不必知道具體的觀察者類,只需調用它的update()方法就行。
這裡,可以稍進一步修改(或許這種修改沒能改變原來的狀況):
將具體觀察者中的持有的主題引用改為Subject類型,而在使用ConcreteSubject時進行類型判斷及強制類型轉換(貌似要加入不少的if語句),《HeadFirst設計模式》中的天氣報告板樣本中是這樣做的,見HeadFirst中的氣象站的實現中的類圖或具體觀察者。
還有一種方式,以上我們同步主題和觀察者的狀態時,使用的是:“state = this.subject.getState();”,這稱為“拉”資料,即觀察者根據自己的需要從主題中擷取資料。然而,或許“推”資料更好一些,即將主題的狀態作為參數通過update(State)傳送給觀察者(不管你用不用,把資料都給你啦),同樣,《HeadFirst設計模式》中的天氣報告板樣本中是這樣做的,見HeadFirst中的氣象站的實現中的類圖或具體觀察者,java內建的對觀察者模式的支援中也提供了兩種選擇:推或拉,見下面的內容:java對觀察者模式的內建支援。
(5)測試
public class Client{public static void main(String[] args){ConcreteSubject concreteSubject = new ConcreteSubject();ConcreteObserver o1 = new ConcreteObserver(concreteSubject);ConcreteObserver o2 = new ConcreteObserver(concreteSubject);concreteSubject.notifyObservers();o1.unRegister();o2.unRegister();concreteSubject.notifyObservers();}}
六、java對觀察者模式的內建支援
java.util.Observable類充當觀察者模式中的抽象主題角色(在這裡可以將其稱為“可觀察者”)
java.util.Observer介面充當觀察者模式中的抽象觀察者角色(體現了“松耦合”)
java內建的對觀察者模式的支援結構圖為:
不足之處(摘自HeadFirst):java.util.Observable是一個抽象類別,就像我們前面所提到的,因為java是單繼承的,這使得某類不可能同時具有Observable和其他超類的行為,這限制了Observable的複用潛力(這也是在代碼實現1中使用interface的原因)。另外,Observable將關鍵方法如setChanged()設定成protected,這意味著,:除非你繼承Observable類,否則你無法建立Observable執行個體並組合到你自己的對象中來,這違反了“多用組合,少用繼承”的原則。
(一個樣本:《使用java內建的支援實現HeadFirst氣象站》)
七、推、拉資料
“推(push)”資料指的是主題將狀態資訊(資料)作為參數通過update(State)方法(在Observer介面中定義)傳給具體觀察者,具體觀察者再根據需要使用參數State中有用的資訊進行同步更新。
“拉(pull)”資料指的是接到通知後,觀察者根據需要從主題中提取自己需要的資料。
在java內建的對觀察者模式的支援中,也提供了這兩種狀態(資料)的傳遞的方式:
java.util.Observer介面(即抽象觀察者角色)中有且只有一個方法:void update(Observable o, Object arg),其中參數arg傳遞的就是主題(可觀察者)的狀態資訊。
java.util.Observable類(可觀察者,即我們的“抽象主題角色”)中方法notifyObservers()就是對“拉”資料的支援,方法notifyObservers(Object arg)就是對 “推”資料的支援。
八、設計原則
設計原則:為了互動對象之間的松耦合設計而努力。
在觀察者模式中,改變主題或觀察者其中一方,並不會影響另一方,因為兩者是松耦合的,所以只要他們之間的介面仍被遵守,我們就可以自由地改變他們。
松耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應付變化,是因為對象之間的互相依賴降到了最低。
九、其他
1.上述的主題和觀察者中的狀態(資料)State泛指主題(或觀察者)中的狀態資訊,可以是一組資料,並不是只有一個Object類型的狀態資料State。
2.根據自己的需要適當實現觀察者模式,如:抽象主題的實現方式(介面或抽象類別。)是否在主題或觀察者中加入表示本身狀態的屬性、是否在觀察者中加入一個主題類型的引用(這個引用的類型是抽象主題類型的還是具體主題類型的。)、選擇傳遞資料的方式(推或拉。)以及是否採用類似Observable中setChanged()方法適當調整主題通知觀察者的程度(是立即通知還是達到一定程度才通知還是..?)等等。
3.對於更加複雜的依賴關係的觀察者模式,《設計模式》中進行了闡述,摘抄如下
封裝複雜的更新語義:
當目標和觀察者間的依賴關係特別複雜時, 可能需要一個維護這些關係的對象。我們稱這樣的對象為更改管理器(ChangeManager)。它的目的是盡量減少觀察者反映其目標的狀態變化所需的工作量。例如, 如果一個操作涉及到對幾個相互依賴的目標進行改動, 就必須保證僅在所有的目標都已更改完畢後,才一次性地通知它們的觀察者,而不是每個目標都通知觀察者。
ChangeManager有三個責任:
a) 它將一個目標映射到它的觀察者並提供一個介面來維護這個映射。這就不需要由目標來維護對其觀察者的引用, 反之亦然。
b) 它定義一個特定的更新策略。
c) 根據一個目標的請求, 它更新所有依賴於這個目標的觀察者。
下頁的框圖描述了一個簡單的基於ChangeManager的Observer模式的實現。有兩種特殊的ChangeManager。SimpleChangeManager總是更新每一個目標的所有觀察者, 比較簡單。相反,DAGChangeManager處理目標及其觀察者之間依賴關係構成的無環有向圖。當一個觀察者觀察多個目標時, DAGChangeManager要比SimpleChangeManager更好一些。在這種情況下, 兩個或更多個目標中產生的改變可能會產生冗餘的更新。DAGChangeManager保證觀察者僅接收一個更新。當然,當不存在多重更新的問題時, SimpleChangeManager更好一些。ChangeManager是一個Mediator(中介者)模式的執行個體。通常只有一個ChangeManager, 並且它是全域可見的。這裡Singleton(單例)模式可能有用。
轉載請註明出處:http://blog.csdn.net/jialinqiang/article/details/8871965