觀察者模式是一種經常使用的設計模式,在軟體系統中對象並不是孤立存在的,一個對象行為的改變可能會導致其他與之存在依賴關係的對象行為發生改變,觀察者模式用於描述對象之間的依賴關係。 模式動機 很多情況下,對象不是孤立存在的,想象這麼一個場景:你和女朋友去旅行,晚上回到賓館,女朋友穿著厚厚的大衣,從外表看 ...
觀察者模式是一種經常使用的設計模式,在軟體系統中對象並不是孤立存在的,一個對象行為的改變可能會導致其他與之存在依賴關係的對象行為發生改變,觀察者模式用於描述對象之間的依賴關係。
模式動機
很多情況下,對象不是孤立存在的,想象這麼一個場景:你和女朋友去旅行,晚上回到賓館,女朋友穿著厚厚的大衣,從外表看上去就是個臃腫的包子。你沒有反應,等到女朋友洗完澡裹著浴巾出來以後,你立馬眼睛都瞪直了,活脫脫一個大色狼(不...不要盯著人家看了啦)
從例子中,我們不難分離出兩類角色,一類為觀察者,如色眯眯的你,另一類就是被觀察者所觀察的目標,如性感的女朋友。如果觀察目標發生某個動作,觀察者就會有響應。
建立一種對象與對象之間的依賴關係,一個對象發生改變時會自動通知其他對象,其他對象將相應做出反應。發生改變的對象稱為觀察目標,被通知的對象稱為觀察者,一個觀察目標可以對應多個觀察者,而且觀察者之間沒有相互聯繫,可以根據需要增加和刪除觀察者,這就是觀察者模式的模式動機。
模式定義
定義對象間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知並自動更新。觀察者模式又叫發佈-訂閱模式(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)或從屬者模式(Dependents)模式。觀察者模式是一種對象行為型模式。
模式分析
觀察者模式描述瞭如何建立對象與對象之間的依賴關係,那麼具體如何構造呢?
這一模式的關鍵對象是觀察目標和觀察者,一個目標可以有任意多個與之相依賴的觀察者,一旦目標狀態發生變化,所有觀察者都將得到通知。
作為對這個通知的響應,每個觀察者都將即時更新自己的狀態,與目標狀態同步,這種交互也稱發佈-訂閱(publish-subscribe)。
目標是通知的發佈者,它發出通知時不需要知道誰是它的觀察者,可以有任意數目的觀察訂閱並接收通知。
由此看一下根據該模式得出的類圖
抽象目標 Subject,典型代碼如下
import java.util.*;
public abstract class Subject {
// 定義一個集合存儲任意數量的觀察者對象
protected ArrayList<Observer> observers = new ArrayList<Observer>();
// 增加一個觀察者
public abstract void attach(Observer observer);
// 刪除一個觀察者
public abstract void detach(Observer observer);
// 通知各個觀察者並調用它們的 update() 方法
public abstract void notifyObservers();
}
具體目標類 ConcreteSubject 是實現 Subject 的一個具體子類,典型代碼如下
public class ConcreteSubject extends Subject {
// 向集合中添加一個觀察者
public void attach(Observer observer) {
observers.add(observer);
}
// 從集合中刪除一個觀察者
public void detach(Observer observer) {
observers.remove(observer);
}
// 迴圈調用集合中觀察者的 updates() 方法
public void notifyObservers() {
for (Observer obs : observers) {
obs.update();
}
}
}
也可以把 attach() 和 detach() 方法的實現放在 Subject 中,這樣就無需每個子類都去實現一次了(如果沒有特別需求的話)。另外,它還可以實現在目標類中定義的抽象業務邏輯方法(如果有的話)
抽象觀察者一般定義為一個介面,其中聲明 update() 方法,這個方法在其子類中實現,不同的觀察者具有不同的更新響應方法,典型代碼如下
public interface Observer {
public void update();
}
具體觀察者 ConcreteObserver 中實現 update() 方法,其典型代碼如下
public class ConcreteObserver implements Observer {
public void update() {
// 具體更新代碼
}
}
在使用時,客戶端首先創建具體目標對象以及具體觀察者對象(也可以使用配置文件),然後,調用目標對象的 attach() 方法,將這個觀察者對象在目標對象中登記,也就是將它加入到目標對象的觀察者集合中
Subject subject = new ConcreteSubject();
Observer observer = new ConcreteObserver();
subject.attach(observer);
subject.notifyObservers();
客戶端調用目標對象的 notifyObservers() 方法時,將調用在其觀察者集合中註冊的觀察者對象的 update() 方法,實現狀態的更新
當然,在有些複雜的情況下,具體觀察者類 ConcreteObserver 的 update() 方法在執行時,需要使用到 ConcreteSubject 中的狀態(屬性)
舉個例子,我們經常會用可視化的圖表(如柱狀圖、餅狀圖)來顯示數據,同樣的數據可能有不同的圖表顯示方法(即不同的具體觀察者),如果數據發生改變,圖標也應該實時做出改變,那自然的,圖表在更新時肯定需要用到新的數據吧
如果像上述而言,那麼 ConcreteObserver 和 ConcreteSubject 之間還存在關聯關係,在 ConcreteObserver 中定義一個 ConcreteSubject 實例,通過該實例獲取存儲在 ConcreteSubject 中的狀態屬性。但這樣一來,系統的擴展性將受到一定影響,增加新的具體目標類有時候需要修改原有觀察者的代碼,在一定程度上違反了開閉原則,但如果原有觀察者無須關聯新增具體目標,則系統擴展性不受影響
// 具體目標類
public class ConcreteSubject extends Subject {
// 方便起見,attahch() 等抽象方法在抽象層做了實現了
// 具體目標類狀態
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
}
// 具體觀察者類
public class ConcreteObserver implements Observer {
private Subject subject = new ConcreteSubject();
private int observerState;
public void update() {
observerState = subject.getState();
// 具體更新代碼
}
}
模式優缺點
觀察者模式的優點
- 在觀察目標和觀察者之間建立一個抽象的耦合,觀察目標不需要瞭解其具體觀察者,只需知道它們都有一個共同的介面即可
- 觀察者模式支持廣播通信,觀察目標會向所有註冊的觀察者發出通知,簡化一對多系統設計的難度
- 觀察者模式符合開閉原則,增加新的觀察者無須修改原有系統代碼,在具體觀察者和觀察目標之間不存在關聯關係的情況下,增加新的目標也很方便
觀察者模式的缺點
- 如果一個觀察目標有很多直接和間接的觀察者,將所有觀察者都通知到會花費很多時間
- 如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間的迴圈調用,可能導致系統崩潰
- 觀察者模式沒有相應機制讓觀察者知道所觀察目標是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化
Java 對觀察者模式的支持
Java 提供了 Observable 類以及 Observer 介面,構成對 Java 語言對觀察者模式的支持
java.util.Observer 介面只定義一個方法,充當抽象觀察者
public interface Observer {
void update(Observable o, Object arg);
}
當觀察目標狀態發生變化時,該方法將被調用,在 Observer 的實現子類實現該 update() 方法,即具體觀察者可以根據需要有不同的更新行為。當調用觀察目標類 Observable 的 notifyObservers() 方法時,將調用觀察者類中的 update() 方法。
java.util.Observable 類充當觀察目標類,定義了一個向量 Vector 來存儲觀察者對象。標記變數 changed 表示觀察目標是否發生變化,true 表示發生變化,false 則表示對象不再發生改變還已經通知了所有觀察者對象,並調用了它們的 update() 方法。
我們可以直接使用 Observer 介面和 Observable 類來作為觀察者模式的抽象層,自定義具體的觀察者類和觀察目標類,更加方便地在 Java 語言中使用觀察者模式。