別再面向 for 循環編程了,JDK 自帶的觀察者模式就很香!

你們好,你還在面向 for 循環編程嗎?java

還有誰不會用觀察者模式嗎?git

本篇棧長帶來《觀察者模式》理論及實戰~github

什麼是觀察者模式?

觀察者模式(Observer Pattern)定義了對象間的一種一對多的依賴關係,這樣只要一個對象的狀態發生改變,其依賴的全部相關對象都會獲得通知並自動更新。編程

在觀察者模式中,發生改變的對象叫作觀察目標,而被通知更新的對象稱爲觀察者,一個觀察目標對應多個觀察者,觀察者通常是一個列表集合,能夠根據須要動態增長和刪除,易於擴展。設計模式

使用觀察者模式的優勢在於觀察目標和觀察者之間是抽象鬆耦合關係,下降了二者之間的耦合關係。安全

發佈-訂閱模式

觀察者模式不少地方也叫發佈-訂閱模式(Publish/Subscribe),其實也能夠這麼理解,不過二者之間仍是略有不一樣。微信

觀察者模式中的觀察者是直接綁定觀察目標,觀察目標要維護一套觀察者列表,二者是有一個基於接口的組合依賴關係的,因此說觀察者模式雖然是鬆耦合的,但並非徹底解耦的。app

發佈-訂閱模式中的發佈者和訂閱者二者並無任何聯繫,發佈者經過中間方發佈一個主題(Topic),訂閱者經過中間方(調度中心)訂閱一個主題(Topic),發佈者狀態的變動也並不會直接通知訂閱者,而要經過中間方進行通知,或者訂閱者自行從中間方拉取,因此說發佈-訂閱模式是徹底解耦的。異步

一圖搞懂它們的關係:ide

從圖片看二者是有差異的,統一都叫觀察者模式,也沒毛病。

觀察者模式輪子

因觀察者模式應用比較普遍,因此 JDK 工具包從 1.0 版本里面自帶了觀察者模式模板套裝,咱們根據其模板很方便就能實現觀察者模式,不須要再重複造輪子了。

觀察者目標類:

java.util.Observable

裏面兩個最重要的變量:

  • changed:觀察目標狀態是否變動,默認爲:false;
  • obs:觀察者列表(observers),一個線程安全的列表集合:Vector,默認爲空集合;

裏面的重要的方法都是和觀察目標狀態和觀察者相關的,一看就清楚,這裏就不介紹了。

觀察者接口:

java.util.Observable

public interface 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>
     *                 method.
     */
    void update(Observable o, Object arg);
}

觀察者接口只有一個 update 方法,用來通知觀察者本身更新。

觀察者模式實戰

OK,知道了 JDK 自帶了這兩個東東,如今就來實現一個簡單的觀察者模式的應用場景,模擬公衆號文章推送,觀察目標是棧長我,觀察者是大家你們,我在公衆號Java技術棧推一篇文章,大家都能接收到更新通知並能閱讀。

新增觀察目標類:

import lombok.Getter;

import java.util.Observable;

/**
 * 觀察目標:棧長
 * 來源微信公衆號:Java技術棧
 */
@Getter
public class JavaStackObservable extends Observable {

    private String article;

    /**
     * 發表文章
     * @param article
     */
    public void publish(String article){
        // 發表文章
        this.article = article;

        // 改變狀態
        this.setChanged();

        // 通知全部觀察者
        this.notifyObservers();
    }

}

觀察目標的邏輯是先發表文章,再改變觀察目標的狀態,再通知全部觀察者。

咱們來重點看 notifyObservers 方法的源碼:

先獲取同步鎖,判斷狀態是否更新,如已更新則清空觀察目標狀態,而後再使用 for 循環遍歷全部觀察者,一一調用觀察者的更新方法通知觀察者更新。

新增觀察者類:

import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import java.util.Observable;
import java.util.Observer;

/**
 * 觀察者:讀者粉絲
 * 來源微信公衆號:Java技術棧
 */
@RequiredArgsConstructor
public class ReaderObserver implements Observer {

    @NonNull
    private String name;

    private String article;

    @Override
    public void update(Observable o, Object arg) {
        // 更新文章
        updateArticle(o);
    }

    private void updateArticle(Observable o) {
        JavaStackObservable javaStackObservable = (JavaStackObservable) o;
        this.article = javaStackObservable.getArticle();
        System.out.printf("我是讀者:%s,文章已更新爲:%s\n", this.name, this.article);
    }

}

觀察者的邏輯是獲取到觀察者目標實例對象,而後再用觀察目標對象的文章信息更新爲本身的文章信息,最後輸出某某某的文章已更新。

觀察者只要實現 Observer 這個接口的 update 方法便可,用於觀察目標進行調用通知。

本節教程全部實戰源碼已上傳到這個倉庫:https://github.com/javastacks/javastack

觀察目標和觀察者類結構圖以下:

新增測試類:

/**
 * 觀察者:讀者粉絲
 * 來源微信公衆號:Java技術棧
 */
public class ObserverTest {

    public static void main(String[] args) {
        // 建立一個觀察目標
        JavaStackObservable javaStackObservable = new JavaStackObservable();

        // 添加觀察者
        javaStackObservable.addObserver(new ReaderObserver("小明"));
        javaStackObservable.addObserver(new ReaderObserver("小張"));
        javaStackObservable.addObserver(new ReaderObserver("小愛"));

        // 發表文章
        javaStackObservable.publish("什麼是觀察者模式?");
    }

}

觀察目標、觀察者的建立並無前後順序要求,重點是發表文章通知觀察者以前,觀察目標要添加觀察者列表這一步不能少。

輸出結果:

經過這個簡單的文章推送實踐,你們應該對觀察者模式有一個基本的認知了,在實際工做當中也能夠有不少場景拿去用,就一對多的依賴關係均可以考慮使用觀察者模式。

總結

不容易啊,陸陸續續又肝了大半天,你學會觀察者模式了嗎?

觀察者模式的優勢是爲了給觀察目標和觀察者解耦,而缺點也很明顯,從上面的例子也能夠看出,若是觀察者對象太多的話,有可能會形成內存泄露。

另外,從性能上面考慮,全部觀察者的更新都是在一個循環中排隊進行的,因此觀察者的更新操做能夠考慮作成線程異步(或者可使用線程池)的方式,以提高總體效率。

本節教程全部實戰源碼已上傳到這個倉庫:

https://github.com/javastacks/javastack

好了,今天的分享就到這裏了,後面棧長我會更新其餘設計模式的實戰文章,公衆號Java技術棧第一時間推送。Java技術棧《設計模式》系列文章陸續更新中,請你們持續關注哦!

最後,以爲個人文章對你用收穫的話,動動小手,給個在看、轉發,原創不易,棧長鬚要你的鼓勵。

版權申明:本文系公衆號 "Java技術棧" 原創,原創實屬不易,轉載、引用本文內容請註明出處,禁止抄襲、洗稿,請自重,尊重他人勞動成果和知識產權。

相關文章
相關標籤/搜索