概述
觀察者模式是對(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è)簡單的示意性實(shí)現(xiàn)為例石抡,討論觀察者模式的結(jié)構(gòu)。
觀察者模式所涉及的角色有:
1.抽象主題(Subject)角色:
抽象主題角色把所有對(duì)觀察者對(duì)象的引用保存在一個(gè)聚集(比如ArrayList對(duì)象)里助泽,每個(gè)主題都可以有任何數(shù)量的觀察者啰扛。抽象主題提供一個(gè)接口嚎京,可以增加和刪除觀察者對(duì)象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
2.具體主題(ConcreteSubject)角色:
將有關(guān)狀態(tài)存入具體觀察者對(duì)象;在具體主題的內(nèi)部狀態(tài)改變時(shí)方援,給所有登記過的觀察者發(fā)出通知分衫。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
3.抽象觀察者(Observer)角色:
為所有的具體觀察者定義一個(gè)接口珠叔,在得到主題的通知時(shí)更新自己,這個(gè)接口叫做更新接口。
4.具體觀察者(ConcreteObserver)角色:
存儲(chǔ)與主題的狀態(tài)自恰的狀態(tài)蚓曼。具體觀察者角色實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài) 像協(xié)調(diào)钦扭。如果需要纫版,具體觀察者角色可以保持一個(gè)指向具體主題對(duì)象的引用。
源碼
自己編寫的觀察者模式的代碼
/**
* 抽象的主題(被觀察)角色
*/
public abstract class Subject {
/**
* 保存的觀察者集合
*/
private List<Observer> observerList;
public Subject(){
observerList = new ArrayList();
}
/**
* 注冊(cè)觀察者
* @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);
}
/**
* 具體的主題對(duì)象
*/
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ā)生改變時(shí)梭伐,
* 將調(diào)用所有訂閱者的update方法,并將更新后的狀態(tài)作為參數(shù)傳入
* @param state
*/
public void update(String state);
}
/**
* 具體的觀察者對(duì)象
*/
public class ConcreteObserver implements Observer {
private String state;
@Override
public void update(String newState) {
state = newState;
System.out.println("觀察者對(duì)象改變:" + newState);
}
}
/**
* 具體的觀察者對(duì)象
*/
public class ConcreteObserver2 implements Observer {
private String state;
@Override
public void update(String newState) {
state = newState;
System.out.println("觀察者對(duì)象改變2:" + newState);
}
}
/**
* 客戶端
*/
public class Client {
public static void main(String[] args) {
//新建主題對(duì)象
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);
//主題對(duì)象狀態(tài)改變
subject.change("狀態(tài)1");
System.out.println("------------------");
//移除觀察者1
subject.removeObserver(observer1);
subject.change("狀態(tài)2");
}
}
推模型和拉模型
在觀察者模式中仰担,又分為推模型和拉模型兩種方式糊识。
推模型
主題對(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è)引用來獲取了吠卷。
兩種模式的比較
推模型是假定主題對(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)用這一方法灭将。
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ì)象可以更新自己洼怔。
源碼分析
/**
* 主題對(duì)象,繼承Observable類
*/
public class ConcreteSubject extends Observable {
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
setChanged();
notifyObservers();
}
}
/**
* 具體的觀察者左驾,實(shí)現(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);
//主題對(duì)象狀態(tài)改變
subject.setData("test2");
subject.setData("test3");
}
}