一、 概述
觀察者模式是對象的行為模式,又叫發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
將一個系統分割成一個一些類相互協作的類有一個不好的副作用,那就是需要維護相關對象間的一致性。我們不希望為了維持一致性而使各類緊密耦合,這樣會給維護、擴展和重用都帶來不便。觀察者就是解決這類的耦合關系的。
二、 觀察者模式結構
涉及到的角色分別為
- 抽象主題(Subject)角色:抽象主題角色把所有對觀察者對象的引用保存在一個聚集(比如ArrayList對象)里,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
- 具體主題(ConcreteSubject)角色:將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
- 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己,這個接口叫做更新接口。
- 具體觀察者(ConcreteObserver)角色:存儲與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用。
三、 代碼示例
抽象觀察者
提供觀察者狀態變更方法
public interface Observer {
/**
* 更新觀察者狀態方法
* @param state 新的狀態
*/
public void update(String state);
}
抽象主題者
抽象主題定義觀察者對象列表,提供新增和刪除觀察者方法,并通知觀察者狀態變化
public abstract class Subject {
/**
* 觀察者對象列表
*/
private List<Observer> list = new ArrayList<Observer>();
/**
* 新增觀察者,將觀察者對象放入觀察者對象列表中
* @param observer 觀察者對象
*/
public void attach (Observer observer) {
list.add(observer);
System.out.println("Attached new observer");
}
/**
* 刪除觀察者,從觀察者對象列表中刪除觀察者
* @param observer 觀察者對象
*/
public void detach (Observer observer) {
list.remove(observer);
System.out.println("Detach a observer");
}
/**
* 通知觀察者狀態變更,逐個遍歷觀察者對象列表,調用觀察者狀態變更方法
* @param newState 新的狀態
*/
public void nodifyObservers(String newState) {
for (Observer observer : list) {
observer.update(newState);
}
}
}
具體主題對象
提供狀態定義,狀態變更方法,調用觀察者通知方法
public class ConcreateSubject extends Subject {
/**
* 對象狀態
*/
private String state;
/**
* 獲取狀態
* @return 返回狀態值
*/
public String getState() {
return state;
}
/**
* 狀態變更方法,修改狀態,并將狀態通知觀察者對象
* @param newState 新的狀態
*/
public void change(String newState) {
this.state = newState;
System.out.println("State change to " + this.state);
this.nodifyObservers(this.state);
}
}
具體觀察者對象
定義觀察者狀態,實現具體處理業務
public class ConcreateObserver implements Observer {
//觀察者狀態
private String observerState;
/**
* 更新觀察者狀態方法
* @param state 新的狀態
*/
public void update(String state) {
this.observerState = state;
System.out.println("Observer state is " + this.observerState);
}
}
在創建了一個客戶端調用代碼后:
public class Client {
public static void main(String[] args) {
//創建具體主題角色類的實例
ConcreateSubject subject = new ConcreateSubject();
//創建觀察者角色實例
Observer observer = new ConcreateObserver();
//將觀察者對象登記到主題角色對象上
subject.attach(observer);
//修改主題角色狀態
subject.change("State1");
}
}
執行結果為:
Attached new observer
State change to State1
Observer state is State1
首先需要創建具體主題對象,創建觀察者對象,并將觀察者對象注冊到主題對象中。
然后在調用主題對象的修改狀態方法時,先會修改主題對象中狀態值,然后調用父類中觀察者通知方法nodifyObservers()
,逐個循環通知所有觀察者列表中的觀察者狀態變更。
推模型和拉模型
在觀察者模式中,又分為推模型和拉模型兩種方式。
- 推模型
主題對象向觀察者推送主題的詳細信息,不管觀察者是否需要,推送的信息通常是主題對象的全部或部分數據。 - 拉模型
主題對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到主題對象中獲取,相當于是觀察者從主題對象中拉數據。一般這種模型的實現中,會把主題對象自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取數據的時候,就可以通過這個引用來獲取了。
根據上面的描述,發現前面的例子就是典型的推模型,下面給出一個拉模型的實例。
拉模型的抽象觀察者類
拉模型通常都是把主題對象當做參數傳遞。
public interface ObserverGet {
/**
* 直接傳入主題對象,觀察者使用主題對象獲取主題對象中需要的信息
* @param subject 主題對象
*/
public void update(Subject subject);
}
拉模型的具體觀察者對象
public class ConcreateObserverGet implements ObserverGet {
private String observerState;
public void update(Subject subject) {
//保證對象同目標對象類型一致
observerState = ((ConcreateSubject)subject).getState();
System.out.println("ObserverGet state is " + this.observerState);
}
}
拉模型的抽象主題對象
由于拉模型獲取的內容為具體的主題對象,因此需要在推送主題對象狀態時,給出具體主題對象,由觀察者對象根據具體主題對象進行業務處理。
public abstract class Subject {
/**
* 觀察者對象列表
*/
private List<ObserverGet> list = new ArrayList<ObserverGet>();
/**
* 新增觀察者,將觀察者對象放入觀察者對象列表中
* @param observerGet 觀察者對象
*/
public void attach (ObserverGet observerGet) {
list.add(observerGet);
System.out.println("Attached new observer");
}
/**
* 刪除觀察者,從觀察者對象列表中刪除觀察者
* @param observerGet 觀察者對象
*/
public void detach (ObserverGet observerGet) {
list.remove(observerGet);
System.out.println("Detach a observer");
}
/**
* 通知觀察者狀態變更,逐個遍歷觀察者對象列表,調用觀察者狀態變更方法
*/
public void nodifyObservers() {
for (ObserverGet observer : list) {
observer.update(this);
}
}
}
執行結果為:
Attached new observer
State change to State1
ObserverGet state is State1
同推模型相比,拉模型在進行觀察者狀態通知時,就不需要傳遞參數,直接傳遞對象本身即可。
兩種模式的比較
- 推模型是假定主題對象知道觀察者需要的數據;而拉模型是主題對象不知道觀察者具體需要什么數據,沒有辦法的情況下,干脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
- 推模型可能會使得觀察者對象難以復用,因為觀察者的update()方法是按需要定義的參數,可能無法兼顧沒有考慮到的使用情況。這就意味著出現新情況的時候,就可能提供新的update()方法,或者是干脆重新實現觀察者;而拉模型就不會造成這樣的情況,因為拉模型下,update()方法的參數是主題對象本身,這基本上是主題對象能傳遞的最大數據集合了,基本上可以適應各種情況的需要。
JAVA提供的對觀察者模式的支持
在JAVA語言的java.util庫里面,提供了一個Observable類以及一個Observer接口,構成JAVA語言對觀察者模式的支持。
Observer接口
這個接口只定義了一個方法,即update()方法,當被觀察者對象的狀態發生變化時,被觀察者對象的notifyObservers()方法就會調用這一方法。
public interface Observer {
void update(Observable o, Object arg);
}
Observable類
被觀察者類都是java.util.Observable類的子類。java.util.Observable提供公開的方法支持觀察者對象,這些方法中有兩個對Observable的子類非常重要:一個是setChanged(),另一個是notifyObservers()。
第一方法setChanged()被調用之后會設置一個內部標記變量,代表被觀察者對象的狀態發生了變化。第二個是notifyObservers(),這個方法被調用時,會調用所有登記過的觀察者對象的update()方法,使這些觀察者對象可以更新自己。
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
/**
* 新增一個觀察者對象到觀察者對象列表中,如果觀察者對象列表為空,則提示空指針異常
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* 從觀察者對象列表中刪除一個觀察者對象
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
/**
* 如果調用方法`hasChanged()`表明對象已改變,則通知所有注冊觀察者對象
* 通過調用觀察者對象的`update()`方法。
* 然后調用方法`clearChanged()`復位對象修改狀態,重新開始判斷
*/
public void notifyObservers(Object arg) {
/*
* 一個數組,保存當前正在進行監控的觀察者對象
*/
Object[] arrLocal;
synchronized (this) {
/* 我們不希望觀察者對象在保持監控的時候回調任何代碼
* 這個方法將會提取每個觀察者對象的狀態,因此需要使用同步事務鎖
* 但是通知觀察者對象方法不需要
* 最糟糕的情況是下面兩種
* 1) 新增的觀察者對象將會錯過正在進行中的通知
* 2) 一個最近未注冊的觀察者將在其不需要的時候收到通知
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
/**
* 清空觀察者對象列表
*/
public synchronized void deleteObservers() {
obs.removeAllElements();
}
/**
* 當被觀察的對象,也就是主題對象發生變更時,調用此方法
* 然后調用`hasChanged()`方法就會返回true
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* 重置變化狀態為未變化
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* 判斷主題對象是否發生改變
*/
public synchronized boolean hasChanged() {
return changed;
}
/**
* 得到觀察者對象列表中觀察者對象數量
*/
public synchronized int countObservers() {
return obs.size();
}
}
這個類代表一個被觀察者對象,有時稱之為主題對象。一個被觀察者對象可以有數個觀察者對象,每個觀察者對象都是實現Observer接口的對象。在被觀察者發生變化時,會調用Observable的notifyObservers()方法,此方法調用所有的具體觀察者的update()方法,從而使所有的觀察者都被通知更新自己。
怎樣使用JAVA對觀察者模式的支持
這里給出一個非常簡單的例子,說明怎樣使用JAVA所提供的對觀察者模式的支持。
在這個例子中,被觀察對象叫做Watched;而觀察者對象叫做Watcher。Watched對象繼承自java.util.Observable類;而Watcher對象實現了java.util.Observer接口。另外有一個Test類扮演客戶端角色。
被觀察者對象類,繼承Observable
public class Watched extends Observable {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
if (this.data != data) {
this.data = data;
//對象值發生變更,設置對象已發生改變
setChanged();
System.out.println("Data changed to : " + data);
}
//通知觀察者對象們
notifyObservers();
}
}
創建具體觀察者對象類,實現Observer接口
public class Watcher implements java.util.Observer {
public Watcher(Observable observable) {
observable.addObserver(this);
}
public void update(Observable o, Object arg) {
System.out.println("Data hasChanged to : " + ((Watched)o).getData());
}
}
測試代碼
public class TestObserver {
public static void main(String[] args) {
//創建被觀察對象,也就是具體主題對象
Watched watched = new Watched();
//創建具體觀察者對象,并且將觀察者對象添加到被觀察者對象的觀察者兌現列表中
java.util.Observer watcher = new Watcher(watched);
//修改被觀察者對象內容
watched.setData("START");
watched.setData("END");
watched.setData("STOP");
}
}
執行結果為:
Data changed to : START
Data hasChanged to : START
Data changed to : END
Data hasChanged to : END
Data changed to : STOP
Data hasChanged to : STOP
首先創建被觀察對象Watched,然后創建觀察者對象watcher,并且調用構造方法,添加到被觀察者對象的觀察者對象列表中。
在被觀察對象修改內容時,在內容變化的情況下,修改被觀察對象的變更狀態為已改變。然后調用通知觀察者對象方法notifyObservers,傳遞自身對象。
觀察者對象接受到update方法的調用后,處理被觀察者對象,并獲取其中內容。
參考:
《JAVA與模式》之觀察者模式