觀察者模式,從公眾號群發說起
- 2019 年 10 月 3 日
- 筆記
每個人應該都訂閱了不少微信公眾號,那你有沒有注意到微信公眾號的消息呢?你訂閱的公眾號號主每發布一篇文章,你都會主動的接收到文章的推送,並不需要你點開每個訂閱的公眾號一一查看有沒有更新,是不是覺得有點意思?感興趣?那就接著往下看吧,因為接下來我們要模擬公眾號群發的場景。
要模擬公眾號群發,首先需要簡單的了解一下公眾號的特點,對於公眾號的特點,我總結了以下三點:
- 每個公眾號會有多名訂閱者,公眾號跟訂閱者在某種層面上是一對多的關係
- 只有訂閱者才能在公眾號發布新文章時,會及時接收到推送通知,沒有訂閱公眾號的閱讀者不會接收到文章推送通知。
- 每個訂閱者都依賴於公眾號號主,只有公眾號號主發布文章,訂閱者才有文章查看
現在業務場景我們大概知道了,那就開始動手編寫我們的業務吧,我們先從公眾號號主開始。
對於公眾號號主,我們先理解一下公眾號特點的第二點:只有訂閱者才能在公眾號發布新文章時,會及時接收到推送通知,沒有訂閱公眾號的閱讀者不會接收到文章推送通知。這個特點說明在公眾號號主這邊維護者訂閱者的列表,在每次發布文章時會通知列表中的每一個訂閱者告訴他們有新文章了。如果號主沒有訂閱者列表,那怎麼知道需要通知哪些人呢?對於這個訂閱者列表,號主肯定有增刪的權利,畢竟這個公眾號你說了算。根據上面分析的,我們給號主做出一個抽象,我們建立一個抽象的Author
類。Author
類具體設計如下:
/** * 號主抽象類 */ public interface Author { // 添加關注者 void addReader(Reader reader); // 刪除關注者 void deleteReader(Reader reader); // 通知關注者 void notifyReader(); // 寫文章 void writeArticle(String article); }
在我們的場景中號主主要有添加訂閱者、刪除訂閱者、通知訂閱者和寫文章的功能,號主有了,接下來就是我們的訂閱者,訂閱者在我們的場景中就比較簡單了,只有一個閱讀的功能,我們定義一個閱讀者抽象類Reader
,Reader
類的具體設計如下:
/** * 閱讀者介面 */ public interface Reader { // 閱讀文章 void reader(String authorName,String article); }
號主和閱讀者的介面都定義好了,下面我們就需要真正的號主和訂閱者了。我們建立我們第一個號主平頭哥PingtougeAuthor
,PingtougeAuthor
類的設計如下:
/** * @author 平頭哥 * @title: PingtougeAuthor * @projectName observer * @description: 號主平頭哥 * @date 2019/9/1817:50 */ public class PingtougeAuthor implements Author{ // 訂閱者列表 private Vector<Reader> readers ; // 作者名稱 private String name; // 文章 private String article; public PingtougeAuthor(String name){ this.name = name; this.readers = new Vector<>(); } /** * 添加關注者 * @param reader */ @Override public void addReader(Reader reader) { if (readers.contains(reader)) return; readers.add(reader); } /** * 移除關注者 * @param reader */ @Override public void deleteReader(Reader reader) { readers.remove(reader); } /** * 通知關注者 */ @Override public void notifyReader() { for (Reader reader:readers){ reader.reader(name,article); } } /** * 寫文章 * @param article */ @Override public void writeArticle(String article){ this.article = article; notifyReader(); } }
我們在建立王山、張三、李四三個訂閱者,他們的具體設計如下:
/** * 訂閱者王山 */ public class WangSanReader implements Reader{ private String name; public WangSanReader(String name){ this.name = name; } @Override public void reader(String authorName,String article) { System.out.println(name+" 閱讀了 "+authorName+" 發布的 "+article+" 文章"); } }
/** * 訂閱者張三 */ public class ZhangsanReader implements Reader{ private String name; public ZhangsanReader(String name){ this.name = name; } @Override public void reader(String authorName,String article) { System.out.println(name+" 閱讀了 "+authorName+" 發布的 "+article+" 文章"); } }
/** * 訂閱者者李四 */ public class LiSiReader implements Reader{ private String name; public LiSiReader(String name){ this.name = name; } @Override public void reader(String authorName,String article) { System.out.println(name+" 閱讀了 "+authorName+" 發布的 "+article+" 文章"); } }
號主和訂閱者都有了,萬事俱備只欠東風,那我們就來進行我們第一次場景模擬,在這次模擬中,訂閱者張三、王山訂閱了平頭哥的公眾號,李四沒有訂閱平頭哥的公眾號。按照我們的設想平頭哥發布文章時,張三、王山可以接收到文章推送通知,李四不會接收到文章推送通知。編寫我們的第一個模擬類:
public class App { public static void main(String[] args) { PingtougeAuthor pingtougeAuthor = new PingtougeAuthor("平頭哥"); WangSanReader wangSanReader = new WangSanReader("王山"); ZhangsanReader zhangsanReader = new ZhangsanReader("張三"); LiSiReader liSiReader = new LiSiReader("李四"); // 王山訂閱了平頭哥的公眾號 pingtougeAuthor.addReader(wangSanReader); // 張三訂閱了平頭哥的公眾號 pingtougeAuthor.addReader(zhangsanReader); pingtougeAuthor.writeArticle("看完這篇你還不知道這些隊列,我這些圖白作了"); } }
測試結果:
從測試結果中,我們可以看出張三、王山接收到了平頭哥發布文章的推送通知,李四沒有接收到,符合我們的預期。
接下來我們進行第二次模擬,由於平頭哥最近發了不少乾貨,李四決定也關注平頭哥的公眾號,所以我們對模擬類進行了修改,修改後的模擬類如下:
public class App { public static void main(String[] args) { PingtougeAuthor pingtougeAuthor = new PingtougeAuthor("平頭哥"); WangSanReader wangSanReader = new WangSanReader("王山"); ZhangsanReader zhangsanReader = new ZhangsanReader("張三"); LiSiReader liSiReader = new LiSiReader("李四"); // 王山訂閱了平頭哥的公眾號 pingtougeAuthor.addReader(wangSanReader); // 張三訂閱了平頭哥的公眾號 pingtougeAuthor.addReader(zhangsanReader); // // pingtougeAuthor.writeArticle("看完這篇你還不知道這些隊列,我這些圖白作了"); // 李四也訂閱平頭哥的公眾號 pingtougeAuthor.addReader(liSiReader); pingtougeAuthor.writeArticle("實現 Java 本地快取,該從這幾點開始"); } }
測試結果:
這次三個訂閱者都接收到了平頭哥發布文章的推送通知。關注了平頭哥的公眾號有一段時間後,張三覺得平頭哥的公眾號不適合自己,於是就取關了平頭哥的公眾號,這是我們要模擬的第三個場景,我們對模擬類進行修改,修改後的模擬類如下:
public class App { public static void main(String[] args) { PingtougeAuthor pingtougeAuthor = new PingtougeAuthor("平頭哥"); WangSanReader wangSanReader = new WangSanReader("王山"); ZhangsanReader zhangsanReader = new ZhangsanReader("張三"); LiSiReader liSiReader = new LiSiReader("李四"); // 王山訂閱了平頭哥的公眾號 pingtougeAuthor.addReader(wangSanReader); // 張三訂了平頭哥的公眾號 pingtougeAuthor.addReader(zhangsanReader); // // pingtougeAuthor.writeArticle("看完這篇你還不知道這些隊列,我這些圖白作了"); // 李四訂了平頭哥的公眾號 pingtougeAuthor.addReader(liSiReader); // pingtougeAuthor.writeArticle("實現 Java 本地快取,該從這幾點開始"); // 張三取關了平頭哥的公眾號 pingtougeAuthor.deleteReader(zhangsanReader); pingtougeAuthor.writeArticle("實觀察者模式,從微信公眾號說起"); } }
測試結果:
張三取關之後,平頭哥發布的文章,他將不會再接收到推送通知。在上面公眾號群發的模擬場景中,我們使用到了一種設計模式,叫做觀察者模式,那今天我們就一起來簡單的了解一下觀察者模式。
觀察者模式定義
觀察者模式又叫發布-訂閱模式,發布-訂閱模式大家應該非常熟悉了吧,消息中間件就是發布-訂閱模式,觀察者模式主要也是應用在消息中間件。觀察這模式定義了一種一對多的依賴關係讓多個訂閱者同時監聽某一個對象主題,這個主題對象在狀態發生變化時,會通知所有的訂閱者,讓他們自己更新自己。這些特點在我們的模擬場景中基本上都體現出來了,如果你對這些有疑問,可以多看我們的模擬場景。
觀察者的結構
- 抽象主題(Subject)角色:抽取出了主題所需要定義的介面,比如我們的
Author
類 - 具體主題(Concrete Subject)角色:具體的主題實現者,該類必須繼承抽象主題,比如我們的
PingtougeAuthor
- 抽象觀察者(Observer)角色:觀察者抽象類,定義觀察者需要的介面,比如我們的
Reader
- 具體觀察者(Concrete Observer)角色:具體的觀察者,做這具體業務的類,比如我們的三個訂閱者
觀察者的 UML 圖
觀察者模式的優點
- 主題與觀察者之間松耦合
- 支撐廣播通訊:一條消息可以通知給多個人
- 建立一條觸發鏈:A觸發B,B觸發C,一條觸發鏈,不過這個需要注意的地方很多
觀察者的缺點
- 當觀察者對象很多時,通知的發布會花費很多時間,影響程式的效率
- 如果採用順序通知,當某個觀察者卡住了,其他的觀察者將無法接收到通知
- 如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰
觀察者模式差不多就這些內容,相對來說比較容易理解,另外多說一句,在JDK中已經內置了觀察者模式,在java.util
下的Observable
、Observer
兩個類,有興趣的可以去了解一下。
文章不足之處,望大家多多指點,共同學習,共同進步
最後