設(shè)計模式——觀察者模式

在閻宏博士的《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ā)生改變時,所有訂閱它的觀察者都接到通知并自動更新。

ObserverPattern.png

觀察者模式由四個角色組成:

  • 抽象觀察者角色(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)崩潰
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容