別再面向 for 循環編程了,JDK 自帶的觀察者模式就很香!
大家好,你還在面向 for 循環編程嗎?
還有誰不會用觀察者模式嗎?
本篇棧長帶來《觀察者模式》理論及實戰~
什麼是觀察者模式?
觀察者模式(Observer Pattern)定義了對象間的一種一對多的依賴關係,這樣只要一個對象的狀態發生改變,其依賴的所有相關對象都會得到通知並自動更新。
在觀察者模式中,發生改變的對象叫做觀察目標,而被通知更新的對象稱爲觀察者,一個觀察目標對應多個觀察者,觀察者一般是一個列表集合,可以根據需要動態增加和刪除,易於擴展。
使用觀察者模式的優點在於觀察目標和觀察者之間是抽象松耦合關係,降低了兩者之間的耦合關係。
發佈 - 訂閱模式
觀察者模式很多地方也叫發佈-訂閱模式(Publish/Subscribe),其實也可以這麼理解,不過兩者之間還是略有不同。
觀察者模式中的觀察者是直接綁定觀察目標,觀察目標要維護一套觀察者列表,兩者是有一個基於接口的組合依賴關係的,所以說觀察者模式雖然是松耦合的,但並不是完全解耦的。
而發佈-訂閱模式中的發佈者和訂閱者兩者並沒有任何聯繫,發佈者通過中間方發佈一個主題(Topic),訂閱者通過中間方(調度中心)訂閱一個主題(Topic),發佈者狀態的變更也並不會直接通知訂閱者,而要通過中間方進行通知,或者訂閱者自行從中間方拉取,所以說發佈-訂閱模式是完全解耦的。
一圖搞懂它們的關係:
觀察者模式和訂閱發佈模式的區別
從圖片看兩者是有差別的,統一都叫觀察者模式,也沒毛病。
觀察者模式輪子
因觀察者模式應用比較廣泛,所以 JDK 工具包從 1.0 版本里面自帶了觀察者模式模板套裝,我們根據其模板很方便就能實現觀察者模式,不需要再重複造輪子了。
觀察者目標類:
java.util.Observable
裏面兩個最重要的變量:
-
changed:觀察目標狀態是否變更,默認爲:false;
-
obs:觀察者列表(observers),一個線程安全的列表集合:Vector,默認爲空集合;
裏面的重要的方法都是和觀察目標狀態和觀察者相關的,一看就清楚,這裏就不介紹了。
觀察者接口:
java.util.Observable
1public interface Observer {
2 /**
3 * This method is called whenever the observed object is changed. An
4 * application calls an <tt>Observable</tt> object's
5 * <code>notifyObservers</code> method to have all the object's
6 * observers notified of the change.
7 *
8 * @param o the observable object.
9 * @param arg an argument passed to the <code>notifyObservers</code>
10 * method.
11 */
12 void update(Observable o, Object arg);
13}
14
15
觀察者接口只有一個 update 方法,用來通知觀察者自己更新。
觀察者模式實戰
OK,知道了 JDK 自帶了這兩個東東,現在就來實現一個簡單的觀察者模式的應用場景,模擬公衆號文章推送,觀察目標是棧長我,觀察者是你們大家,我在公衆號 Java 技術棧推一篇文章,你們都能接收到更新通知並能閱讀。
新增觀察目標類:
1import lombok.Getter;
2
3import java.util.Observable;
4
5/**
6 * 觀察目標:棧長
7 * 來源微信公衆號:Java技術棧
8 */
9@Getter
10public class JavaStackObservable extends Observable {
11
12 private String article;
13
14 /**
15 * 發表文章
16 * @param article
17 */
18 public void publish(String article){
19 // 發表文章
20 this.article = article;
21
22 // 改變狀態
23 this.setChanged();
24
25 // 通知所有觀察者
26 this.notifyObservers();
27 }
28
29}
30
31
觀察目標的邏輯是先發表文章,再改變觀察目標的狀態,再通知所有觀察者。
我們來重點看 notifyObservers 方法的源碼:
先獲取同步鎖,判斷狀態是否更新,如已更新則清空觀察目標狀態,然後再使用 for 循環遍歷所有觀察者,一一調用觀察者的更新方法通知觀察者更新。
新增觀察者類:
1import lombok.NonNull;
2import lombok.RequiredArgsConstructor;
3
4import java.util.Observable;
5import java.util.Observer;
6
7/**
8 * 觀察者:讀者粉絲
9 * 來源微信公衆號:Java技術棧
10 */
11@RequiredArgsConstructor
12public class ReaderObserver implements Observer {
13
14 @NonNull
15 private String name;
16
17 private String article;
18
19 @Override
20 public void update(Observable o, Object arg) {
21 // 更新文章
22 updateArticle(o);
23 }
24
25 private void updateArticle(Observable o) {
26 JavaStackObservable javaStackObservable = (JavaStackObservable) o;
27 this.article = javaStackObservable.getArticle();
28 System.out.printf("我是讀者:%s,文章已更新爲:%s\n", this.name, this.article);
29 }
30
31}
32
33
觀察者的邏輯是獲取到觀察者目標實例對象,然後再用觀察目標對象的文章信息更新爲自己的文章信息,最後輸出某某某的文章已更新。
觀察者只要實現 Observer 這個接口的 update 方法即可,用於觀察目標進行調用通知。
本節教程所有實戰源碼已上傳到這個倉庫:https://github.com/javastacks/javastack
觀察目標和觀察者類結構圖如下:
新增測試類:
1/**
2 * 觀察者:讀者粉絲
3 * 來源微信公衆號:Java技術棧
4 */
5public class ObserverTest {
6
7 public static void main(String[] args) {
8 // 創建一個觀察目標
9 JavaStackObservable javaStackObservable = new JavaStackObservable();
10
11 // 添加觀察者
12 javaStackObservable.addObserver(new ReaderObserver("小明"));
13 javaStackObservable.addObserver(new ReaderObserver("小張"));
14 javaStackObservable.addObserver(new ReaderObserver("小愛"));
15
16 // 發表文章
17 javaStackObservable.publish("什麼是觀察者模式?");
18 }
19
20}
21
22
觀察目標、觀察者的創建並沒有先後順序要求,重點是發表文章通知觀察者之前,觀察目標要添加觀察者列表這一步不能少。
輸出結果:
通過這個簡單的文章推送實踐,大家應該對觀察者模式有一個基本的認知了,在實際工作當中也可以有很多場景拿去用,就一對多的依賴關係都可以考慮使用觀察者模式。
總結
不容易啊,陸陸續續又肝了大半天,你學會觀察者模式了嗎?
觀察者模式的優點是爲了給觀察目標和觀察者解耦,而缺點也很明顯,從上面的例子也可以看出,如果觀察者對象太多的話,有可能會造成內存泄露。
另外,從性能上面考慮,所有觀察者的更新都是在一個循環中排隊進行的,所以觀察者的更新操作可以考慮做成線程異步(或者可以使用線程池)的方式,以提升整體效率。
本節教程所有實戰源碼已上傳到這個倉庫:
https://github.com/javastacks/javastack
好了,今天的分享就到這裏了,後面棧長我會更新其他設計模式的實戰文章,公衆號 Java 技術棧第一時間推送。Java 技術棧《設計模式》系列文章陸續更新中,請大家持續關注哦!
最後,覺得我的文章對你用收穫的話,動動小手,給個在看、轉發,原創不易,棧長需要你的鼓勵。
版權申明:本文系公衆號 "Java 技術棧" 原創,原創實屬不易,轉載、引用本文內容請註明出處,禁止抄襲、洗稿,請自重,尊重他人勞動成果和知識產權。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/nEYhChMpLQhdZmTuo8jW6A