概述
觀察者模式是對象的行為模式,又叫發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個主題對象在狀態(tài)上發(fā)生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
觀察者模式的結(jié)構(gòu)
一個軟件系統(tǒng)里面包含了各種對象,就像一片欣欣向榮的森林充滿了各種生物一樣。在一片森林中,各種生物彼此依賴和約束,形成一個個生物鏈。一種生物的狀態(tài)變化會造成其他一些生物的相應(yīng)行動,每一個生物都處于別的生物的互動之中。
同樣,一個軟件系統(tǒng)常常要求在某一個對象的狀態(tài)發(fā)生變化的時候,某些其他的對象做出相應(yīng)的改變。做到這一點的設(shè)計方案有很多,但是為了使系統(tǒng)能夠易于復(fù)用,應(yīng)該選擇低耦合度的設(shè)計方案。減少對象之間的耦合有利于系統(tǒng)的復(fù)用,但是同時設(shè)計師需要使這些低耦合度的對象之間能夠維持行動的協(xié)調(diào)一致,保證高度的協(xié)作。觀察者模式是滿足這一要求的各種設(shè)計方案中最重要的一種。
下面以一個簡單的示意性實現(xiàn)為例,討論觀察者模式的結(jié)構(gòu)。
觀察者模式所涉及的角色有:
1.抽象主題(Subject)角色:
抽象主題角色把所有對觀察者對象的引用保存在一個聚集(比如ArrayList對象)里,每個主題都可以有任何數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
2.具體主題(ConcreteSubject)角色:
將有關(guān)狀態(tài)存入具體觀察者對象;在具體主題的內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
3.抽象觀察者(Observer)角色:
為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己,這個接口叫做更新接口。
4.具體觀察者(ConcreteObserver)角色:
存儲與主題的狀態(tài)自恰的狀態(tài)。具體觀察者角色實現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài) 像協(xié)調(diào)。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用。
源碼
自己編寫的觀察者模式的代碼
/**
* 抽象的主題(被觀察)角色
*/
public abstract class Subject {
/**
* 保存的觀察者集合
*/
private List<Observer> observerList;
public Subject(){
observerList = new ArrayList();
}
/**
* 注冊觀察者
* @param observer
*/
public void registerObserver(Observer observer) {
observerList.add(observer);
}
/**
* 刪除觀察者
* @param observer
*/
public void removeObserver(Observer observer) {
observerList.remove(observer);
}
/**
* 通知方法,通知所有觀察者
* @param newState
*/
public void notifyObservers(String newState){
for(Observer observer: observerList){
observer.update(newState);
}
}
public abstract void change(String newState);
}
/**
* 具體的主題對象
*/
public class ConcreteSubject extends Subject {
/**
* 狀態(tài)
*/
private String state;
public String getState() {
return state;
}
/**
* 狀態(tài)改變方法,通知所有的觀察者
* @param newState
*/
@Override
public void change(String newState){
this.state = newState;
System.out.println("主題角色狀態(tài)改變:" + newState);
super.notifyObservers(newState);
}
}
/**
* 抽象的觀察者角色
*/
public interface Observer {
/**
* 更新的接口,由主題角色調(diào)用,當(dāng)主題角色狀態(tài)發(fā)生改變時,
* 將調(diào)用所有訂閱者的update方法,并將更新后的狀態(tài)作為參數(shù)傳入
* @param state
*/
public void update(String state);
}
/**
* 具體的觀察者對象
*/
public class ConcreteObserver implements Observer {
private String state;
@Override
public void update(String newState) {
state = newState;
System.out.println("觀察者對象改變:" + newState);
}
}
/**
* 具體的觀察者對象
*/
public class ConcreteObserver2 implements Observer {
private String state;
@Override
public void update(String newState) {
state = newState;
System.out.println("觀察者對象改變2:" + newState);
}
}
/**
* 客戶端
*/
public class Client {
public static void main(String[] args) {
//新建主題對象
Subject subject = new ConcreteSubject();
//觀察者1
Observer observer1 = new ConcreteObserver();
//觀察者2
Observer observer2 = new ConcreteObserver();
//觀察者3
Observer observer3 = new ConcreteObserver2();
//訂閱
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.registerObserver(observer3);
//主題對象狀態(tài)改變
subject.change("狀態(tài)1");
System.out.println("------------------");
//移除觀察者1
subject.removeObserver(observer1);
subject.change("狀態(tài)2");
}
}
推模型和拉模型
在觀察者模式中,又分為推模型和拉模型兩種方式。
推模型
主題對象向觀察者推送主題的詳細(xì)信息,不管觀察者是否需要,推送的信息通常是主題對象的全部或部分?jǐn)?shù)據(jù)。拉模型
主題對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到主題對象中獲取,相當(dāng)于是觀察者從主題對象中拉數(shù)據(jù)。一般這種模型的實現(xiàn)中,會把主題對象自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取數(shù)據(jù)的時候,就可以通過這個引用來獲取了。
兩種模式的比較
推模型是假定主題對象知道觀察者需要的數(shù)據(jù);而拉模型是主題對象不知道觀察者具體需要什么數(shù)據(jù),沒有辦法的情況下,干脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
推模型可能會使得觀察者對象難以復(fù)用,因為觀察者的update()方法是按需要定義的參數(shù),可能無法兼顧沒有考慮到的使用情況。這就意味著出現(xiàn)新情況的時候,就可能提供新的update()方法,或者是干脆重新實現(xiàn)觀察者;而拉模型就不會造成這樣的情況,因為拉模型下,update()方法的參數(shù)是主題對象本身,這基本上是主題對象能傳遞的最大數(shù)據(jù)集合了,基本上可以適應(yīng)各種情況的需要。
JAVA提供的對觀察者模式的支持
在JAVA語言的java.util庫里面,提供了一個Observable類以及一個Observer接口,構(gòu)成JAVA語言對觀察者模式的支持。
Observer接口
這個接口只定義了一個方法,即update()方法,當(dāng)被觀察者對象的狀態(tài)發(fā)生變化時,被觀察者對象的notifyObservers()方法就會調(diào)用這一方法。
Observable類
被觀察者類都是java.util.Observable類的子類。java.util.Observable提供公開的方法支持觀察者對象,這些方法中有兩個對Observable的子類非常重要:一個是setChanged(),另一個是notifyObservers()。第一方法setChanged()被調(diào)用之后會設(shè)置一個內(nèi)部標(biāo)記變量,代表被觀察者對象的狀態(tài)發(fā)生了變化。第二個是notifyObservers(),這個方法被調(diào)用時,會調(diào)用所有登記過的觀察者對象的update()方法,使這些觀察者對象可以更新自己。
源碼分析
/**
* 主題對象,繼承Observable類
*/
public class ConcreteSubject extends Observable {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
setChanged();
notifyObservers();
}
}
/**
* 具體的觀察者,實現(xiàn)Observer接口
*/
public class ConcreteObserver implements Observer {
/**
* 構(gòu)造方法中訂閱主題
* @param observable
*/
public ConcreteObserver(Observable observable){
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
System.out.println("觀察者觀察到狀態(tài)改變,為:" + ((ConcreteSubject)o).getData());
}
}
/**
* 客戶端
*/
public class Client {
public static void main(String[] args) {
//新增主題
ConcreteSubject subject = new ConcreteSubject();
//新增觀察者
ConcreteObserver observer = new ConcreteObserver(subject);
//主題對象狀態(tài)改變
subject.setData("test2");
subject.setData("test3");
}
}