本篇文章介紹一種設(shè)計(jì)模式——觀察者模式。觀察者模式在Android中有著很多的應(yīng)用,如按鈕監(jiān)聽,接口回調(diào)以及很多流行框架(RxJava,EventBus)中使用。本篇文章內(nèi)容參考《JAVA與模式》之觀察者模式。
一、觀察者模式的概念
觀察者模式是對(duì)象的行為模式,又叫發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象。這個(gè)主題對(duì)象在狀態(tài)上發(fā)生變化時(shí),會(huì)通知所有觀察者對(duì)象,使它們能夠自動(dòng)更新自己。
二、觀察者模式的結(jié)構(gòu)
一個(gè)軟件系統(tǒng)里面包含了各種對(duì)象,就像一片欣欣向榮的森林充滿了各種生物一樣。在一片森林中,各種生物彼此依賴和約束,形成一個(gè)個(gè)生物鏈。一種生物的狀態(tài)變化會(huì)造成其他一些生物的相應(yīng)行動(dòng),每一個(gè)生物都處于別的生物的互動(dòng)之中。
同樣,一個(gè)軟件系統(tǒng)常常要求在某一個(gè)對(duì)象的狀態(tài)發(fā)生變化的時(shí)候,某些其他的對(duì)象做出相應(yīng)的改變。做到這一點(diǎn)的設(shè)計(jì)方案有很多,但是為了使系統(tǒng)能夠易于復(fù)用,應(yīng)該選擇低耦合度的設(shè)計(jì)方案。減少對(duì)象之間的耦合有利于系統(tǒng)的復(fù)用,但是同時(shí)設(shè)計(jì)師需要使這些低耦合度的對(duì)象之間能夠維持行動(dòng)的協(xié)調(diào)一致,保證高度的協(xié)作。觀察者模式是滿足這一要求的各種設(shè)計(jì)方案中最重要的一種。
下面以一個(gè)簡(jiǎn)單的示意性實(shí)現(xiàn)為例,討論觀察者模式的結(jié)構(gòu)。

觀察者模式所涉及的角色有:
● 抽象主題(Subject)角色:抽象主題角色把所有對(duì)觀察者對(duì)象的引用保存在一個(gè)聚集(比如ArrayList對(duì)象)里,每個(gè)主題都可以有任何數(shù)量的觀察者。抽象主題提供一個(gè)接口,可以增加和刪除觀察者對(duì)象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
● 具體主題(ConcreteSubject)角色:將有關(guān)狀態(tài)存入具體觀察者對(duì)象;在具體主題的內(nèi)部狀態(tài)改變時(shí),給所有登記過的觀察者發(fā)出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
● 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個(gè)接口,在得到主題的通知時(shí)更新自己,這個(gè)接口叫做更新接口。
● 具體觀察者(ConcreteObserver)角色:存儲(chǔ)與主題的狀態(tài)自恰的狀態(tài)。具體觀察者角色實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。如果需要,具體觀察者角色可以保持一個(gè)指向具體主題對(duì)象的引用。
三、觀察者模式的代碼實(shí)現(xiàn)
抽象主題角色類(此處是抽象類,改成接口將會(huì)提高復(fù)用)
public abstract class Subject {
/**
* 用來保存注冊(cè)的觀察者對(duì)象
*/
private List<Observer> list = new ArrayList<Observer>();
/**
* 注冊(cè)觀察者對(duì)象
* @param observer 觀察者對(duì)象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 刪除觀察者對(duì)象
* @param observer 觀察者對(duì)象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有注冊(cè)的觀察者對(duì)象
*/
public void nodifyObservers(String newState){
for(Observer observer : list){
observer.update(newState);
}
}
}
具體主題角色類
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主題狀態(tài)為:" + state);
//狀態(tài)發(fā)生改變,通知各個(gè)觀察者
this.nodifyObservers(state);
}
}
抽象觀察者角色類
public interface Observer {
/**
* 更新接口
* @param state 更新的狀態(tài)
*/
public void update(String state);
}
具體觀察者角色類
public class ConcreteObserver implements Observer {
//觀察者的狀態(tài)
private String observerState;
@Override
public void update(String state) {
/**
* 更新觀察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致
*/
observerState = state;
System.out.println("狀態(tài)為:"+observerState);
}
}
具體使用:
public class Client {
public static void main(String[] args) {
//創(chuàng)建主題對(duì)象
ConcreteSubject subject = new ConcreteSubject();
//創(chuàng)建觀察者對(duì)象
Observer observer = new ConcreteObserver();
//將觀察者對(duì)象登記到主題對(duì)象上
subject.attach(observer);
//改變主題對(duì)象的狀態(tài)
subject.change("new state");
}
}
當(dāng)主題對(duì)象的狀態(tài)改變時(shí),將通知所有觀察者,觀察者接收到主題對(duì)象的通知后,將可以進(jìn)行其他操作,進(jìn)行響應(yīng)。
四、推模型和拉模型
在觀察者模式中,又分為推模型和拉模型兩種方式。
● 推模型
主題對(duì)象向觀察者推送主題的詳細(xì)信息,不管觀察者是否需要,推送的信息通常是主題對(duì)象的全部或部分?jǐn)?shù)據(jù)。
● 拉模型
主題對(duì)象在通知觀察者的時(shí)候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動(dòng)到主題對(duì)象中獲取,相當(dāng)于是觀察者從主題對(duì)象中拉數(shù)據(jù)。一般這種模型的實(shí)現(xiàn)中,會(huì)把主題對(duì)象自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取數(shù)據(jù)的時(shí)候,就可以通過這個(gè)引用來獲取了。
根據(jù)上面的描述,發(fā)現(xiàn)前面的例子就是典型的推模型,下面給出一個(gè)拉模型的實(shí)例。
拉模型的抽象觀察者類
拉模型通常都是把主題對(duì)象當(dāng)做參數(shù)傳遞。
public interface Observer {
/**
* 更新接口
* @param subject 傳入主題對(duì)象,方面獲取相應(yīng)的主題對(duì)象的狀態(tài)
*/
public void update(Subject subject);
}
拉模型的具體觀察者類
public class ConcreteObserver implements Observer {
//觀察者的狀態(tài)
private String observerState;
@Override
public void update(Subject subject) {
/**
* 更新觀察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致
*/
observerState = ((ConcreteSubject)subject).getState();
System.out.println("觀察者狀態(tài)為:"+observerState);
}
}
拉模型的抽象主題類
拉模型的抽象主題類主要的改變是nodifyObservers()方法。在循環(huán)通知觀察者的時(shí)候,也就是循環(huán)調(diào)用觀察者的update()方法的時(shí)候,傳入的參數(shù)不同了。
public abstract class Subject {
/**
* 用來保存注冊(cè)的觀察者對(duì)象
*/
private List<Observer> list = new ArrayList<Observer>();
/**
* 注冊(cè)觀察者對(duì)象
* @param observer 觀察者對(duì)象
*/
public void attach(Observer observer){
list.add(observer);
System.out.println("Attached an observer");
}
/**
* 刪除觀察者對(duì)象
* @param observer 觀察者對(duì)象
*/
public void detach(Observer observer){
list.remove(observer);
}
/**
* 通知所有注冊(cè)的觀察者對(duì)象
*/
public void nodifyObservers(){
for(Observer observer : list){
observer.update(this);
}
}
}
拉模型的具體主題類
跟推模型相比,有一點(diǎn)變化,就是調(diào)用通知觀察者的方法的時(shí)候,不需要傳入?yún)?shù)了。
public class ConcreteSubject extends Subject{
private String state;
public String getState() {
return state;
}
public void change(String newState){
state = newState;
System.out.println("主題狀態(tài)為:" + state);
//狀態(tài)發(fā)生改變,通知各個(gè)觀察者
this.nodifyObservers();
}
}
兩種模式的比較
■ 推模型是假定主題對(duì)象知道觀察者需要的數(shù)據(jù);而拉模型是主題對(duì)象不知道觀察者具體需要什么數(shù)據(jù),沒有辦法的情況下,干脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
■ 推模型可能會(huì)使得觀察者對(duì)象難以復(fù)用,因?yàn)橛^察者的update()方法是按需要定義的參數(shù),可能無法兼顧沒有考慮到的使用情況。這就意味著出現(xiàn)新情況的時(shí)候,就可能提供新的update()方法,或者是干脆重新實(shí)現(xiàn)觀察者;而拉模型就不會(huì)造成這樣的情況,因?yàn)槔P拖拢瑄pdate()方法的參數(shù)是主題對(duì)象本身,這基本上是主題對(duì)象能傳遞的最大數(shù)據(jù)集合了,基本上可以適應(yīng)各種情況的需要。
五、JAVA提供的對(duì)觀察者模式的支持
在JAVA語言的java.util庫里面,提供了一個(gè)Observable類以及一個(gè)Observer接口,構(gòu)成JAVA語言對(duì)觀察者模式的支持。
Observer接口
這個(gè)接口只定義了一個(gè)方法,即update()方法,當(dāng)被觀察者對(duì)象的狀態(tài)發(fā)生變化時(shí),被觀察者對(duì)象的notifyObservers()方法就會(huì)調(diào)用這一方法。
public interface Observer {
void update(Observable o, Object arg);
}
Observable類
被觀察者類都是java.util.Observable類的子類。java.util.Observable提供公開的方法支持觀察者對(duì)象,這些方法中有兩個(gè)對(duì)Observable的子類非常重要:一個(gè)是setChanged(),另一個(gè)是notifyObservers()。第一方法setChanged()被調(diào)用之后會(huì)設(shè)置一個(gè)內(nèi)部標(biāo)記變量,代表被觀察者對(duì)象的狀態(tài)發(fā)生了變化。第二個(gè)是notifyObservers(),這個(gè)方法被調(diào)用時(shí),會(huì)調(diào)用所有登記過的觀察者對(duì)象的update()方法,使這些觀察者對(duì)象可以更新自己。
public class Observable {
private boolean changed = false;
private Vector obs;
/** Construct an Observable with zero Observers. */
public Observable() {
obs = new Vector();
}
/**
* 將一個(gè)觀察者添加到觀察者聚集上面
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
/**
* 將一個(gè)觀察者從觀察者聚集上刪除
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
/**
* 如果本對(duì)象有變化(那時(shí)hasChanged 方法會(huì)返回true)
* 調(diào)用本方法通知所有登記的觀察者,即調(diào)用它們的update()方法
* 傳入this和arg作為參數(shù)
*/
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
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();
}
/**
* 將“已變化”設(shè)置為true
*/
protected synchronized void setChanged() {
changed = true;
}
/**
* 將“已變化”重置為false
*/
protected synchronized void clearChanged() {
changed = false;
}
/**
* 檢測(cè)本對(duì)象是否已變化
*/
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();
}
}
這個(gè)類代表一個(gè)被觀察者對(duì)象,有時(shí)稱之為主題對(duì)象。一個(gè)被觀察者對(duì)象可以有數(shù)個(gè)觀察者對(duì)象,每個(gè)觀察者對(duì)象都是實(shí)現(xiàn)Observer接口的對(duì)象。在被觀察者發(fā)生變化時(shí),會(huì)調(diào)用Observable的notifyObservers()方法,此方法調(diào)用所有的具體觀察者的update()方法,從而使所有的觀察者都被通知更新自己。
使用JAVA對(duì)觀察者模式的支持
這里給出一個(gè)非常簡(jiǎn)單的例子,說明怎樣使用JAVA所提供的對(duì)觀察者模式的支持。在這個(gè)例子中,被觀察對(duì)象叫做Watched;而觀察者對(duì)象叫做Watcher。Watched對(duì)象繼承自java.util.Observable類;而Watcher對(duì)象實(shí)現(xiàn)了java.util.Observer接口。另外有一個(gè)Test類扮演客戶端角色。
被觀察者Watched類
public class Watched extends Observable{
private String data = "";
public String getData() {
return data;
}
public void setData(String data) {
if(!this.data.equals(data)){
this.data = data;
setChanged();
notifyObservers();
}
}
}
觀察者Watcher 類
public class Watcher implements Observer{
public Watcher(Observable o){
o.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
System.out.println("狀態(tài)發(fā)生改變:" + ((Watched)o).getData());
}
}
具體使用:
public static void main(String[] args) {
//創(chuàng)建被觀察者對(duì)象
Watched watched = new Watched();
//創(chuàng)建觀察者對(duì)象,并將被觀察者對(duì)象登記
Observer watcher = new Watcher(watched);
//給被觀察者狀態(tài)賦值
watched.setData("start");
watched.setData("run");
watched.setData("stop");
}
總結(jié):
觀察者模式一般是一對(duì)多的情形,本文中篇幅有限,都是一對(duì)一的例子。當(dāng)一對(duì)多時(shí),通過Java內(nèi)置的觀察者模式時(shí),通知多個(gè)觀察者的順序不是固定的。所以如果依賴此順序的話,要自己實(shí)現(xiàn)觀察者模式。同時(shí)Java內(nèi)置的觀察者模式中,Observerable是個(gè)類,所以在子類繼承了該類后就不能繼承其他類,導(dǎo)致復(fù)用受到限制,自己實(shí)現(xiàn)觀察者模式時(shí)可以設(shè)置為接口,提高復(fù)用。