[設(shè)計模式] 觀察者模式

[設(shè)計模式] 觀察者模式

手機(jī)用戶請橫屏獲取最佳閱讀體驗,REFERENCES中是本文參考的鏈接,如需要鏈接和更多資源,可以關(guān)注其他博客發(fā)布地址。

平臺 地址
CSDN https://blog.csdn.net/sinat_28690417
簡書 http://m.itdecent.cn/u/3032cc862300
個人博客 https://yiyuery.github.io/NoteBooks/

觀察者模式定義了對象之間的一對多依賴,這樣依賴,當(dāng)一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并更新。

對于觀察模式的實現(xiàn)方式有很多種,一般是以包含Subject和Observer接口的類設(shè)計做法最為常見。

根據(jù)多個觀察者對于同一主題信息的獲取方式,可以分為:

  1. 被觀察者(主題)主動推送,一變化,就將被觀察對象推送給所有觀察者。
  2. 觀察者通過接口主動拉取主題中的部分信息,避免無關(guān)信息的干擾。

觀察者模式的好處:

  • 提供了一種對象設(shè)計,讓主題和觀察者之間松耦合
  • 解決一對多的依賴問題

場景分析

加入我們現(xiàn)在有個手機(jī)工廠,每次生產(chǎn)出一批手機(jī)后,就需要通知對應(yīng)的代理商過來拿貨。

我們就可以利用觀察者模式來實現(xiàn)這個場景需求。

  • 手機(jī)工廠就是被觀察對象,也就是主題。
  • 多個代理商分別充當(dāng)不同的觀察者對象。需要監(jiān)聽手機(jī)工廠的
    生產(chǎn)情況。
  • 一旦手機(jī)工廠生產(chǎn)出手機(jī),就會通知我們的代理商過來取貨。

實戰(zhàn)

類圖

在這里插入圖片描述
  • 首先,我們需要定義觀察者和被觀察者接口,并定義通知能力接口。
public interface Observer<T> {

    /**
     * 觀察者接收通知
     */
    void handle(T message);
}

public interface Subject<T> extends INotify{

    /**
     * 注冊觀察者
     * @param observer
     */
    void register(Observer<T> observer);

    /**
     * 注銷觀察者
     * @param observer
     */
    void cancel(Observer<T> observer);

}

public interface INotify {

    /**
     * 通知
     */
    void sendMessage();
}

  • 然后,定義對應(yīng)實例實現(xiàn)觀察者和被觀察者

手機(jī)工廠

public class PhoneFactory implements Subject<Message> {

    private List<Observer<Message>> observers;

    /**
     * 消息體
     */
    private Message message;

    public PhoneFactory() {
        this.observers = new ArrayList<>();
    }

    /**
     * 注冊觀察者
     *
     * @param observer
     */
    @Override
    public void register(Observer<Message> observer) {
        observers.add(observer);
    }

    /**
     * 注銷觀察者
     *
     * @param observer
     */
    @Override
    public void cancel(Observer<Message> observer) {
        observers.remove(observer);
    }

    /**
     * 發(fā)送通知
     */
    @Override
    public void sendMessage() {
        observers.forEach(p->{
            p.handle(message);
        });
    }

    /**
     * 外部設(shè)置消息變更
     * @param message
     */
    public void setMessage(Message message) {
        this.message = message;
        sendMessage();
    }
}

消息體

@Data
@Builder
@AllArgsConstructor
public class Message {

    /**
     * 消息體
     */
    private String data;
}

代理商

//淘寶
public class TaobaoAgent implements Observer<Message> {
    /**
     * 觀察者接收通知
     *
     * @param message
     */
    @Override
    public void handle(Message message) {
        System.out.println("Taobao receive message: " + message.getData());
    }
}
//天貓
public class TianmaoAgent implements Observer<Message> {
    /**
     * 觀察者接收通知
     *
     * @param message
     */
    @Override
    public void handle(Message message) {
        System.out.println("Tianmao receive message: " + message.getData());
    }
}
  • 最后,定義測試類,我們來看下效果

/**
 * 自定義觀察者模式
 */
@Test
public void testX1() {
    //被觀察者
    PhoneFactory phoneFactory = new PhoneFactory();
    //觀察者
    TianmaoAgent tianmaoAgent = new TianmaoAgent();
    TaobaoAgent taobaoAgent = new TaobaoAgent();
    //注冊
    phoneFactory.register(tianmaoAgent);
    phoneFactory.register(taobaoAgent);
    //發(fā)送通知
    phoneFactory.setMessage(Message.builder()
            .data("剛生產(chǎn)了100臺Xiaomi手機(jī)")
            .build());
    //移除觀察者
    phoneFactory.cancel(tianmaoAgent);
    //再次發(fā)送通知
    System.out.println("-------------------");
    phoneFactory.setMessage(Message.builder()
            .data("剛又生產(chǎn)了100臺iPhone")
            .build());

}

打印

Tianmao receive message: 剛生產(chǎn)了100臺Xiaomi手機(jī)
Taobao receive message: 剛生產(chǎn)了100臺Xiaomi手機(jī)
-------------------
Taobao receive message: 剛又生產(chǎn)了100臺iPhone

從輸出可以看出,觀察者和被觀察者有以下幾個特點:

  • 觀察者可以隨時進(jìn)行注冊和注銷行為
  • 被觀察者只會給注冊過的觀察者發(fā)送通知
  • 不同代理商接收通知的順序和自己注冊的先后有關(guān)系

那么思考下這個模式有沒有什么不好的地方?

  • 每次被觀察者發(fā)送消息,觀察者都需要全部接收并處理。如果信息比較復(fù)雜或是觀察者只關(guān)注其中一部分?jǐn)?shù)據(jù)怎么處理?
  • 這種屬于被動等待被觀察者推的行為,如果不是通知,而是其他一些實時數(shù)據(jù)的監(jiān)控呢?如果觀察者不想等待,想直接獲取當(dāng)前實時數(shù)據(jù)信息呢?
  • JDK內(nèi)置了觀察者模式的實現(xiàn),為什么還要自己定義呢?與自定義的有什么區(qū)別呢?

帶著問題,我們來看下JDK內(nèi)置的觀察者模式怎么實現(xiàn)的?并看下如何實現(xiàn)主動拉取被觀察者信息。

JDK內(nèi)置觀察者模式實現(xiàn)

public class PhoneFactory2 extends Observable {

    /**
     * 消息體
     */
    private Message message;


    public void setMessage(Message message) {
        this.message = message;
        setChanged();
        notifyObservers(message);
    }

    /**
     * 觀察者按需主動拉取自己所需要信息
     * @return
     */
    public Message getMessage() {
        return message;
    }
}

public class TaobaoAgent2 implements Observer {

    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param o   the observable object.
     * @param arg an argument passed to the <code>notifyObservers</code>
     */
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Taobao2 receive message: " + arg.toString());
    }
}

public class TianmaoAgent2 implements Observer {

    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param o   the observable object.
     * @param arg an argument passed to the <code>notifyObservers</code>
     */
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("Tianmao2 receive message: " + arg.toString());
    }

}

比較下變化:

  • 首先代碼量上減少了,不需要定義接口
  • notifyObservers方法不用實現(xiàn),直接使用父層方法
  • 使用的是繼承的方式實現(xiàn)觀察者和被觀察者
  • 定義了getMessage方法,可以主動拉取信息
  • 發(fā)送通知前,需要調(diào)用父層方法setChanged

定義下測試類:

/**
 * 使用JDK原生觀察者模式
 */
@Test
public void testX2() {
    //被觀察者
    PhoneFactory2 phoneFactory = new PhoneFactory2();
    //觀察者
    TianmaoAgent2 tianmaoAgent = new TianmaoAgent2();
    TaobaoAgent2 taobaoAgent = new TaobaoAgent2();
    //注冊
    phoneFactory.addObserver(tianmaoAgent);
    phoneFactory.addObserver(taobaoAgent);
    //發(fā)送通知
    phoneFactory.setMessage(Message.builder()
            .data("剛生產(chǎn)了100臺Xiaomi手機(jī)")
            .build());
    //移除觀察者
    phoneFactory.deleteObserver(tianmaoAgent);
    //再次發(fā)送通知
    System.out.println("-------------------");
    phoneFactory.setMessage(Message.builder()
            .data("剛又生產(chǎn)了100臺iPhone")
            .build());
    //主動拉取消息
    System.out.println("pull message:" + phoneFactory.getMessage());
}

看下測試類的輸出:

Taobao2 receive message: Message(data=剛生產(chǎn)了100臺Xiaomi手機(jī))
Tianmao2 receive message: Message(data=剛生產(chǎn)了100臺Xiaomi手機(jī))
-------------------
Taobao2 receive message: Message(data=剛又生產(chǎn)了100臺iPhone)
pull message:Message(data=剛又生產(chǎn)了100臺iPhone)

分析下兩次輸出的區(qū)別:

  • 接收消息的順序反了
  • 被觀察者開放了自身被監(jiān)控屬性的Getter方法

其實乍看之下,兩種方式實現(xiàn)結(jié)果貌似沒什么區(qū)別,但是仔細(xì)觀察JDK內(nèi)置的Observable,不是一個接口,是個類。其內(nèi)部的setChanged方法也是被protected修飾的

protected synchronized void setChanged() {
        changed = true;
}

這意味著:你除非繼承自O(shè)bservable,否則無法創(chuàng)建Observable實例,并組合到你自己的對象中來,這個設(shè)計違反了多用組合,少用繼承的設(shè)計原則。

相較于我們自己實現(xiàn)的觀察者模式,可以結(jié)合泛型定義被通知的消息體,還可以結(jié)合我們的通知接口INotify等方式來擴(kuò)展,開發(fā)自由度更加高。但是無論是哪種方式,關(guān)鍵是熟悉觀察者模式后,善用即可,

REFERENCES

《Head First》讀書筆記

更多

掃碼關(guān)注架構(gòu)探險之道,回復(fù)文章標(biāo)題,獲取本文相關(guān)源碼和資源鏈接

在這里插入圖片描述

知識星球(掃碼加入獲取歷史源碼和文章資源鏈接)

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

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

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