在閻宏博士的《JAVA與模式》一書中開頭是這樣描述觀察者(Observer)模式的:觀察者模式是對象的行為模式,又叫發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個主題對象在狀態(tài)上發(fā)生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
在實際的項目開發(fā)中,觀察者模式是一個使用頻率非常高的模式,通過它的別名:發(fā)布——訂閱模式也能知道它的主要作用就是用來解耦,將觀察者和被觀察者解耦,使它們的依賴性更小。
觀察者模式定義了被觀察者和觀察者之間的一對多的依賴關(guān)系,使得每當(dāng)被觀察者發(fā)生改變時,所有訂閱它的觀察者都接到通知并自動更新。

觀察者模式由四個角色組成:
- 抽象觀察者角色(Observer):為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己,這個接口叫做更新接口。
- 具體觀察者角色(ConcreteObserver):具體觀察者角色實現(xiàn)抽象觀察者角色所要求的更新接口,以便在主題狀態(tài)發(fā)生改變時更新自身狀態(tài)。
- 抽象主題角色(Subject):抽象主題角色把所有對觀察者對象的引用保存在一個聚集(比如ArrayList對象)里,每個主題都可以有任何數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。
- 具體主題角色(ConcreteSubject):該角色將有關(guān)狀態(tài)存入具體觀察者對象,在具體主題的內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。
案例演示
在我們的Android開發(fā)中,我們通過監(jiān)聽聯(lián)系人廣播,然后能及時獲得聯(lián)系人發(fā)出改變的通知,這個就是一個觀察者模式的案例。比如微信公眾號的推送,當(dāng)微信公眾號的主題者發(fā)送一篇文章的時候,就會推送給所有的訂閱者。這里我們就以微信公眾號的例子來演示。
定義抽象訂閱者和具體訂閱者
/**
* 定義觀察者的接口
* @author Iflytek_dsw
*
*/
interface IObserver {
/**
* 定義觀察者的更新方法
*/
public void update(String message);
}
class ConcreteObserver implements IObserver{
private String name;
public ConcreteObserver(String name) {
super();
this.name = name;
}
@Override
public void update(String message) {
System.out.println("訂閱者:" + name + "收到:" + message);
}
}
在上面的抽象訂閱者中,定義了一個update方法用于更新訂閱者的狀態(tài)。
定義抽象被訂閱者
/**
* 定義抽象被訂閱者
* @author Iflytek_dsw
*
*/
abstract class Subject{
/**
* 定義訂閱者集合
*/
protected List<IObserver> listObservers;
/**
* 注冊訂閱者
*/
public abstract void registerObserver(IObserver observer);
/**
* 反注冊訂閱者
*/
public abstract void unregisterObserver(IObserver observer);
/**
* 通知訂閱者改變
*/
public abstract void notifyDateChanged(String message);
}
/**
* 具體的被訂閱者
* @author Iflytek_dsw
*
*/
class AndoterSubject extends Subject{
public AndoterSubject(){
listObservers = new ArrayList<IObserver>();
}
@Override
public void registerObserver(IObserver observer) {
listObservers.add(observer);
}
@Override
public void unregisterObserver(IObserver observer) {
listObservers.remove(observer);
}
@Override
public void notifyDateChanged(String message) {
for(IObserver observer: listObservers){
observer.update(message);
}
}
}
在被觀察者中我們定義了三個方法,注冊訂閱者、注銷訂閱者、通知訂閱者。這三個方法中組合進(jìn)行管理訂閱者。
客戶端使用
public class Client {
public static void main(String []args){
//定義兩個觀察者
IObserver observerJack = new ConcreteObserver("Jack");
IObserver observerLucy = new ConcreteObserver("Lucy");
//定義被觀察者,同時添加觀察者
Subject andoterObservable = new AndoterSubject();
andoterObservable.registerObserver(observerJack);
andoterObservable.registerObserver(observerLucy);
//Observable發(fā)生改變
andoterObservable.notifyDateChanged("發(fā)布文章【設(shè)計模式——觀察者模式】");
}
}
運(yùn)行結(jié)果
訂閱者:Jack收到:發(fā)布文章【設(shè)計模式——觀察者模式】
訂閱者:Lucy收到:發(fā)布文章【設(shè)計模式——觀察者模式】
在上面的例子中,其實按照我們的正常理解,應(yīng)該是觀察者添加被觀察者,由觀察者覺得需要觀察誰?這樣的一個邏輯貌似才合理。如果要達(dá)成這樣的目的,UML圖就需要進(jìn)行變動了。是否可以這樣呢?
JDK中的觀察者模式Observable、Observer
在Java中通過Observable類和Observer接口實現(xiàn)了觀察者模式。一個Observer對象監(jiān)視著一個Observable對象的變化,當(dāng)Observable對象發(fā)生變化時,Observer得到通知,就可以進(jìn)行相應(yīng)的工作。
Observable被觀察者
Observable被觀察者中提供了setChange()、notifyObservers()兩個方法。
- notifyObservers():調(diào)用一個列表中所有的Observer的update()方法,通知它們數(shù)據(jù)發(fā)生了變化。
- setChange():方法用來設(shè)置一個內(nèi)部標(biāo)志位注明數(shù)據(jù)發(fā)生了變化。
- addObserver(Observer o):添加觀察者
- deleteObserver(Observer o):刪除觀察者
- notifyObservers():通知觀察者改變
Observer觀察者
Observer通過Observable的addObserver()方法把自己添加到列表列表中。
同樣適用上面的案例,我們來實現(xiàn)以下。
創(chuàng)建觀察者
class User implements Observer{
private String name;
public User(String name) {
super();
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("訂閱者:" + name + "收到消息:" + arg);
}
}
從上面可以看到,只要實現(xiàn)Observer接口即可完成一個訂閱者類的開發(fā)。
創(chuàng)建被觀察者
class AndoterWX extends Observable{
public void postArtical(String title){
//標(biāo)識狀態(tài)或者內(nèi)容發(fā)生改變
setChanged();
//通知所有觀察者
notifyObservers(title);
}
}
集成Observable類實現(xiàn)一個被觀察者,然后通過setChanged()設(shè)置狀態(tài)改變,通過notiflyObservers來通知觀察者改變。
客戶端
public class Client {
public static void main(String []args){
User Jack = new User("Jack");
User Lucy = new User("Lucy");
AndoterWX andoterWX = new AndoterWX();
andoterWX.addObserver(Jack);
andoterWX.addObserver(Lucy);
andoterWX.postArtical("設(shè)計模式——觀察者模式");
}
}
結(jié)果
訂閱者:Lucy收到消息:設(shè)計模式——觀察者模式
訂閱者:Jack收到消息:設(shè)計模式——觀察者模式
使用觀察者模式的場景和優(yōu)缺點(diǎn)
使用場景
關(guān)聯(lián)行為場景,需要注意的是,關(guān)聯(lián)行為是可拆分的,而不是“組合”關(guān)系。事件多級觸發(fā)場景??缦到y(tǒng)的消息交換場景,如消息隊列、事件總線的處理機(jī)制。
優(yōu)點(diǎn)
- 解除耦合,讓耦合的雙方都依賴于抽象,從而使得各自的變換都不會影響另一邊的變換。觀察者和被觀察者是抽象耦合的。
- 建立一套觸發(fā)機(jī)制。
缺點(diǎn)
- 觀察者過多,將所有的觀察者都通知到會花費(fèi)很多時間
- 如果在觀察者和觀察目標(biāo)之間有循環(huán)依賴的話,觀察目標(biāo)會觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰