觀察者模式(Observer Pattern,物件行為型模式,釋出-訂閱模式 Publish/Subscribe Pattern)
意圖
通知狀態變化
定義物件間的一對多的依賴關係,當一個物件的狀態發生變化時,所有依賴它的物件都得到通知並被自動更新,由抽象類或介面實現。
推模式:目標角色複雜,並且觀察者角色進行更行時必須得到一些具體的變化資訊
拉模式:目標角色簡單
適用性
在以下任一情況下可以使用觀察者模式:
1. 當一個抽象模型有兩個方面,其中一個方面依賴於兩一個方面。將這兩者封裝在獨立物件中以使它們可以各自獨立地改變和複用。
2. 當一個物件的改變需要同時改變其它物件,而不知道具體有多少物件有待改變
3. 當一個物件必須通知其它物件,而它又不能假定其它物件是誰。換言之,比不希望這些物件是緊密耦合的。
結構
參與者
Subject
- 目標指導它的觀察者。可以有任意多個觀察者觀察同一個目標。
- 提供註冊和刪除觀察者物件的介面
ConcreteSubject
- 將有關狀態存入各ConcreteObserver物件
- 當它的狀態發生變化時,向它的各個觀察者發出通知
Observer
1.為那些在目標發生改變時需要獲得通知的物件定義一個更新介面,抽象觀察者角色主要由抽象類或介面實現。
ConcreteObserver
- 維護一個指向ConcreteSubject物件的引用
- 儲存有關狀態,這些狀態應與目標的狀態保持一致
- 實現Observer的更新介面以使自身狀態與目標的狀態保持一致。
程式碼
Subject
public abstract class Subject {
private Vector<Observer> vector = new Vector<Observer>();
public void addObserver(Observer observer){
vector.add(observer);
}
public void deleteObserver(Observer observer){
vector.remove(observer);
}
public void notifyAllObserver(){
for(Observer observer:vector){
observer.update(this);
}
}
public abstract int getNumber();
public abstract void execute();
}
ConcreteSubject
public class ConcreteSubject extends Subject{
private Random random = new Random();
private int number;
public int getNumber(){
return number;
}
public void execute(){
for(int i=0;i<20;i++){
number = random.nextInt(50);
this.notifyAllObserver();
}
}
}
Observer
public interface Observer {
public void update(Subject subject);
}
ConcreteObserver
public class ConcreteObserver1 implements Observer{
public void update(Subject subject) {
System.out.println(subject.getNumber()+"======ConcreteObserver1========");
}
}
public class ConcreteObserver2 implements Observer{
public void update(Subject subject) {
System.out.println(subject.getNumber()+"======ConcreteObserver2========");
}
}
Main
public class Main {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer ob1 = new ConcreteObserver1();
Observer ob2 = new ConcreteObserver2();
subject.addObserver(ob1);
subject.addObserver(ob2);
subject.execute();
}
}
Java內部實現
Observer
/**
* A class can implement the <code>Observer</code> interface when it
* wants to be informed of changes in observable objects.
*
* @author Chris Warth
* @see java.util.Observable
* @since JDK1.0
*/
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}
Observable
/**
* This class represents an observable object, or "data"
* in the model-view paradigm. It can be subclassed to represent an
* object that the application wants to have observed.
* <p>
* An observable object can have one or more observers. An observer
* may be any object that implements interface <tt>Observer</tt>. After an
* observable instance changes, an application calling the
* <code>Observable</code>'s <code>notifyObservers</code> method
* causes all of its observers to be notified of the change by a call
* to their <code>update</code> method.
* <p>
* The order in which notifications will be delivered is unspecified.
* The default implementation provided in the Observable class will
* notify Observers in the order in which they registered interest, but
* subclasses may change this order, use no guaranteed order, deliver
* notifications on separate threads, or may guarantee that their
* subclass follows this order, as they choose.
* <p>
* Note that this notification mechanism has nothing to do with threads
* and is completely separate from the <tt>wait</tt> and <tt>notify</tt>
* mechanism of class <tt>Object</tt>.
* <p>
* When an observable object is newly created, its set of observers is
* empty. Two observers are considered the same if and only if the
* <tt>equals</tt> method returns true for them.
*
* @author Chris Warth
* @see java.util.Observable#notifyObservers()
* @see java.util.Observable#notifyObservers(java.lang.Object)
* @see java.util.Observer
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
* @since JDK1.0
*/
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector<>();
}
/**
* Adds an observer to the set of observers for this object, provided
* that it is not the same as some observer already in the set.
* The order in which notifications will be delivered to multiple
* observers is not specified. See the class comment.
*
* @param o an observer to be added.
* @throws NullPointerException if the parameter o is null.
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* Deletes an observer from the set of observers of this object.
* Passing <CODE>null</CODE> to this method will have no effect.
* @param o the observer to be deleted.
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
/**
* If this object has changed, as indicated by the
* <code>hasChanged</code> method, then notify all of its observers
* and then call the <code>clearChanged</code> method to
* indicate that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two
* arguments: this observable object and <code>null</code>. In other
* words, this method is equivalent to:
* <blockquote><tt>
* notifyObservers(null)</tt></blockquote>
*
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#hasChanged()
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers() {
notifyObservers(null);
}
/**
* If this object has changed, as indicated by the
* <code>hasChanged</code> method, then notify all of its observers
* and then call the <code>clearChanged</code> method to indicate
* that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two
* arguments: this observable object and the <code>arg</code> argument.
*
* @param arg any object.
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#hasChanged()
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;
synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
/**
* Clears the observer list so that this object no longer has any observers.
*/
public synchronized void deleteObservers() {
obs.removeAllElements();
}
/**
* Marks this <tt>Observable</tt> object as having been changed; the
* <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* Indicates that this object has no longer changed, or that it has
* already notified all of its observers of its most recent change,
* so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.
* This method is called automatically by the
* <code>notifyObservers</code> methods.
*
* @see java.util.Observable#notifyObservers()
* @see java.util.Observable#notifyObservers(java.lang.Object)
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* Tests if this object has changed.
*
* @return <code>true</code> if and only if the <code>setChanged</code>
* method has been called more recently than the
* <code>clearChanged</code> method on this object;
* <code>false</code> otherwise.
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#setChanged()
*/
public synchronized boolean hasChanged() {
return changed;
}
/**
* Returns the number of observers of this <tt>Observable</tt> object.
*
* @return the number of observers of this object.
*/
public synchronized int countObservers() {
return obs.size();
}
}
協作
- 當ConcreteSubject發生任何可能導致其觀察者與其本身狀態不一致的改變時,它將通知它的各個觀察者。
- 在得到一個具體目標的改變通知後,ConcreteObserver物件可向目標物件查詢資訊。ConcreteObserver使用這些資訊以使它的狀態與目標物件的狀態一致。
效果
Observer模式允許你獨立的改變目標和觀察者。你可以單獨複用目標物件而無需同時複用其觀察者,反之亦然。它也使你可以在不改動目標和其他觀察者的前提下增加觀察者。
目標和觀察者間的抽象耦合
一個目標所知道的僅僅是它有一系列觀察者,每個都符合抽象的Observer類的簡單介面。目標不知道任何一個觀察者屬於哪一個具體的類。這樣目標和觀察者之間的耦合是抽象的和最小的。
因為目標和觀察者不是緊密耦合的,它們可以屬於一個系統中的不同抽象層次。一個處於較低層次的目標物件可以與一個處於較高層次的觀察者通訊不通知它,這樣就保持了系統層次的完整性。如果目標和觀察者混在一起,那麼得到的物件要麼貫穿兩個層次(違反了層次性),要麼必須放在這層次的某一個層中(這可能會損害層次抽象)。
支援廣播通訊
不像通常的請求,目標傳送的通知不需要指定它的接收者。通知被自動廣播給所有已向該目標物件登記的有關物件。目標物件並不關係到底有多少物件對自己感興趣;它唯一的責任就是通知它的各個觀察者。這給了你在任何時刻增加和刪除觀察者的自由。處理還是忽略一個通知取決於觀察者。
意外的更新
因為一個觀察者並不知道其他觀察者的存在,它可能對改變目標的最終代價一無所知。在目標上一個看似無害的操作可能會引起一系列觀察者以及依賴於這些觀察者的那些物件的更新。此外,如果依賴準則的定義或維護不當,常常會引起錯誤的更新,這種錯誤通常很難捕捉。
簡單的更新協議不提供具體細節說明目標中什麼被改變了,這使得上述問題更加嚴重。如果沒有其他協議幫助觀察者發現什麼發生了改變,它們可能會被迫盡力減少改變。
優點
1.觀察者和被觀察者之間是鬆耦合的,分別可以各自獨立改變。
2. Subject在傳送廣播通知的時候,無須指定具體的Observer,Observer可以自己決定是否要訂閱Subject的通知。
3. 遵守大部分GRASP原則和常用設計原則,高內聚、低耦合。
缺點
- 鬆耦合導致程式碼關係不明顯,有時可能難以理解。
- 如果一個物件被大量觀察者訂閱的話,在廣播通知的時候可能會有效率問題
實現
建立目標到其他觀察者之間的對映
一個目標物件跟蹤它應通知的觀察者的最簡單的方法是顯式地在目標中儲存對它們的引用。然而,當目標很多而觀察者較少時,這樣儲存可能代價太高。一個解決辦法是用時間換取空間,用一個關聯查詢機制(hash)來維護目標到觀察者的對映。這樣一個沒有觀察者的目標就不產生儲存開銷。但另一個方面,這一方法增加了訪問觀察者的開銷。
觀察多個目標
在某些情況下,一個觀察者依賴於多個目標可能是有意義的。例如,一個表格物件可能依賴於多個數據源。在這種情況下,必須擴充套件Update介面以使觀察者知道是哪一個目標送來的通知。目標物件可以簡單地將自己作為Update操作的一個引數,讓觀察者知道應去檢查哪一個目標。
誰觸發更新
目標和它的觀察者依賴於通知機制來保持一致。但是到底哪一個物件呼叫Notify來觸發更新:
1. 由目標物件的狀態設定操作在改變目標物件的狀態後自動呼叫Notify。這種方法的優點是客戶不需要記住要在目標物件上呼叫Notify,缺點是多個連續的操作會產生多次連續的更新,可能效率較低。
2. 讓客戶負責在適當的時候呼叫Notify。這樣做的優點是客戶可以在一系列的狀態改變完成之後再一次性觸發更新,避免了不必要的中間更新。缺點是給客戶增加了觸發更新的責任。由於客戶可能會忘記呼叫Notify,這種方式較容易出差。
對已刪除目標的懸掛引用
刪除一個目標時應該注意不要在其觀察者中遺留對該目標的懸掛引用。一種避免懸掛引用的方法是,當一個目標被刪除時,讓它通知它的觀察者將對該目標的引用復位。一般來說,不能簡單地刪除觀察者,因為其他的物件可能會引用它們,或者也可能它們還在觀察其他的目標。
在發出通知前確保目標的狀態自身是一致的
在發出通知前確保狀態自身一致這一點很重要,因為觀察者在更新其狀態的過程中需要查詢目標的當前狀態。
當Subject的子類呼叫繼承的該項操作時,很容易無意中違反這條自身一致性的準則。可以使用抽象的Subject類中的模板方法傳送通知來避免這種錯誤。定義那些子類可以重定義的原語操作,並將Notify作為模板方法中的最後一個操作,這樣當子類重新定義了Subject的操作時,還可以保證該物件的狀態時自身一致的。
在文件中記錄是哪一個Subject觸發通知總是應該的。
避免特定於觀察者的更新協議-推/拉模式
觀察者模式的實現經常需要讓目標廣播關於其改變的其他一些資訊。目標將這些資訊作為Update操作一個引數傳遞出去。這些資訊的量可能很小,也可能很大。
一個極端情況是,目標向觀察者傳送關於改變的詳細資訊,而不管它們需要與否。我們稱之為推模型。另一個極端是拉模型;目標除最小通知外什麼也不送出,而在此之後由觀察者顯式地向目標詢問細節。
拉模型強調的是目標不知道它的觀察者,而推模型假定目標知道一些觀察者的需要的資訊。推模型可能使得觀察者相對難以複用,因為目標對觀察者的假定可能並不總是正確的。另一個方面。拉模型可能效率較差,因為觀察者物件需在沒有目標物件幫助的情況下確定什麼改變了。
顯式的指定感興趣的改變
你可以擴充套件目標的註冊介面,讓各觀察者註冊為僅對特定事件感興趣,以提高更新的效率。當一個事件發生時,目標僅通知那些已註冊為對該事件感興趣的觀察者。支援這種做法一種途徑是,對使用目標物件的方面的概念。
封裝複雜的更新語義
當目標和觀察者間的依賴關係特別複雜時,可能需要一個維護這些關係的物件。我們稱這樣的物件為更改管理器(ChangeManager)。它的目的是儘量減少觀察者反映其目標的狀態變化所需的工作量。例如,如果一個操作涉及到對幾個相互依賴的目標進行改動,就必須保證僅在所有的目標都已更新完畢後,才一次性的通知它們的觀察者,而不是每個目標都通知觀察者。
ChangeManager的責任:
1. 它將一個目標對映到它的觀察者並提供一個介面來維護這個對映。這就不需要由目標來維護對其觀察者的引用,反之必然。
2. 它定義一個特定的更新策略
3. 根據一個目標的請求,它更新所有依賴於這個目標的觀察者。
ChangeManager是一個Mediator模式的例項。通常只有一個ChangeManager, 並且它是全域性可見的。這裡Singleton模式可能有用。
SimpleChangeManager總是更新每一個目標的所有觀察者,比較簡單。相反,DAGChangeManager處理目標及其觀察者之間依賴關係構成的無環有向圖。
經典例子
Java中已經定義、GUI、業務資料處理
相關模式
Mediator Pattern
Mediator Pattern在進行Mediator參與者和Colleague參與者通訊時可能會用到觀察者模式。Mediator Pattern和觀察者模式在“通知狀態變化“這點很相識,Pattern的目的和觀點角度並不相同。
Mediator Pattern也會通知狀態變化,不過充其量也只是Mediator Pattern的一部分,而Mediator Pattern的主要目的還是在Colleague參與者的協調。Observer Pattern則是放在Observer參與者(可能有多個)通知Subject參與者的狀態,而且在通知後取得同步。
敬請期待“策略模式(Strategy Pattern,物件行為型模式)”