設計模式就該這麽學:以微信訂閱號來講觀察者模式(第三篇)
前言:繼續《設計模式就該這麽學》系列文章,今天以當前比較火的微信訂閱號給大家介紹應用得比較多的一種設計模式——觀察者模式,之後再來介紹java拉模型方式的內置設計模式實現,最後附帶一個項目實際觀察者應用的例子!
《設計模式就該這麽學》系列文章:
設計模式就該這麽學:為什麽要學設計模式?(開篇漫談)
設計模式就該這麽學:要走心才能遵循設計模式五大原則(第二篇)
設計模式就該這麽學:以微信訂閱號來講觀察者模式(第三篇)
觀察者模式實際應用:監聽線程,意外退出線程後自動重啟
一. 什麽是觀察者模式
以《Head First 設計模式》這本書中的定義:
觀察者模式:它定義了對象之間的一(Subject)對多(Observer)的依賴,這樣一來,當一個對象(Subject)改變時,它的所有的依賴者都會收到通知並自動更新。
首先看下觀察者模式的類圖
- 主題(Subject)接口:對象使用此接口註冊為觀察者,或者把自己從觀察者中移除。
- 觀察者(Observer)接口:所有潛在的觀察者都必須實現該接口,這個接口只有update一個方法,他就是在主題狀態發生變化的時候被調用。
- 具體主題(ConcreteSubject)類:它是要實現Subject接口,除了註冊(registerObserver)和撤銷(removeObserver)外,它還有一個通知所有的觀察者(notifyObservers)方法。
- 具體觀察者(ConcreteObserver)類:它是要實現ObserverJ接口,並且要註冊主題,以便接受更新。
二、以微信訂閱號來深入介紹觀察者模式
看了上面定義及類圖好像不太容易理解,微信訂閱號我相信大家都不陌生,接下來就微信訂閱號的例子來介紹下觀察者模式。首先看下面一張圖:
如上圖所示,微信訂閱號就是我們的主題,用戶就是觀察者。他們在這個過程中扮演的角色及作用分別是:
- 訂閱號就是主題,業務就是推送消息
- 觀察者想要接受推送消息,只需要訂閱該主題即可
- 當不再需要消息推送時,取消訂閱號關註即可
- 只要訂閱號還在,觀察者可以一直去進行關註
接下來讓我們通過一段示例,三位同事zhangsai、liyong、liujing訂閱人民日報訂閱號為例來介紹觀察者模式(Obsever),代碼如下:
//1、人民日報接口 public interface PeoplesDaily { //添加訂閱者 void RegisterObserver(Observer observer); //取消訂閱 void RemoveObserver(Observer observer); //發送人民日報 void notifyObservers(); } //2、訂閱者接口 public interface Observer { //有新的人民日報了就會被執行通知 void update(); } //3、人民日報 public class PeopleNewsPaper implements PeoplesDaily { private List<Observer> subList = new List<Observer>(); public void RegisterObserver(Observer observer) { subList.Add(observer); } public void RemoveObserver(Observer observer) { if (subList.IndexOf(observer) >= 0) { subList.Remove(observer); } } //推送人民日報消息了~~ public void notifyObservers() { for (Observer sub : subList) { sub.update(); } } } //4、訂閱者 public class subHuman implements Observer { //訂閱者的名字 private string name; public subHuman(string f_name) { name = f_name; } //通知訂閱者有新人民日報推送消息了 public void update() { system.out.println(p_name + "!! 有新的人民日報消息了,請查收!"); } } //5、測試開始訂閱,和調用了 public static void Main(string[] args) { PeopleNewsPaper paper = new PeopleNewsPaper(); subHuman zhsangsai = new subHuman("張賽"); subHuman liyong = new subHuman("李勇"); subHuman liujin = new subHuman("劉晶"); //張賽訂閱人民日報 paper.RegisterObserver(zhsangsai); //李勇訂閱人民日報 paper.RegisterObserver(liyong); //劉晶訂閱人民日報 paper.RegisterObserver(liujin); //有新人民日報推送消息了 paper.notifyObservers(); system.out.println("---------------發完人民日報了------------------"); //張賽不想訂了,取消人民日報 paper.RemoveObserver(zhsangsai); //又有新人民日報了 就沒有張賽的人民日報 了 paper.notifyObservers(); }
測試結果:
張賽!! 有新的人民日報消息了,請查收! 李勇!! 有新的人民日報消息了,請查收! 劉晶!! 有新的人民日報消息了,請查收! ---------------發完人民日報了------------------ 張賽!! 有新的人民日報消息了,請查收! 李勇!! 有新的人民日報消息了,請查收!
三、再來說設計模式的推拉模型
在觀察者模式中,又分為推模型和拉模型兩種方式。
- 推模型:主題對象向觀察者推送主題的詳細信息,不管觀察者是否需要,每次有新的信息就會推送給它的所有的觀察者。
- 拉模型:主題對象是根據觀察者需要更具體的信息,由觀察者主動到主題對象中獲取,相當於是觀察者從主題對象中拉數據。
而它們的區別在於:
“推”的好處包括:
1、高效。如果沒有更新發生,不會有任何更新消息推送的動作,即每次消息推送都發生在確確實實的更新事件之後,所以這種推送是有意義的。
2、實時。事件發生後的第一時間即可觸發通知操作。
“拉”的好處包括:
1、如果觀察者眾多,那麽主題要維護訂閱者的列表臃腫,把訂閱關系解脫到Observer去完成,什麽時候要自己去拉數據就好了。
2、Observer可以不理會它不關心的變更事件,只需要去獲取自己感興趣的事件即可。
根據上面的描述,發現前面的例子就是典型的推模型,下面我先來介紹下java內置的拉模型設計模式實現,再給出一個拉模型的實例。
在JAVA編程語言的java.util類庫裏面,提供了一個Observable類以及一個Observer接口,用來實現JAVA語言對觀察者模式的支持。
Observer接口:這個接口代表了觀察者對象,它只定義了一個方法,即update()方法,每個觀察者都要實現這個接口。當主題對象的狀態發生變化時,主題對象的notifyObservers()方法就會調用這一方法。
public interface Observer { void update(Observable o, Object arg); }
Observable類:這個類代表了主體對象,主題對象可以有多個觀察者,主題對象發生變化時,會調用Observable的notifyObservers()方法,此方法調用所有的具體觀察者的update()方法,從而使所有的觀察者都被通知更新自己
package java.util; public class Observable { private boolean changed = false; private Vector obs; public Observable() { obs = new Vector<>(); } //添加一個觀察者 public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } //刪除一個觀察者 public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } //通知所有的觀察者 public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } //調用Observer類通知所有的觀察者 for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } //省略...... }
接下來再介紹我用java這種內置的觀察者設計模式在項目中的一個實際應用,詳細請看我的這篇博文:觀察者模式實際應用:監聽線程,意外退出線程後自動重啟
這裏只介紹下思路:
項目場景:用戶那邊會不定期的上傳文件到一個ftp目錄,我需要實現新上傳的文件做一個自動檢測,每次只要有文件新增,自動解析新增文件內容入庫,並且要保證該功能的穩定性!!
實現思路:
1、監聽器初始化創建:首先在tomcat啟動的時候,利用監聽器初始化創建一個監控文件新增線程,如下:
@Component public class ThreadStartUpListenser implements ServletContextListener { //監控文件新增線程 private static WatchFilePathTask r = new WatchFilePathTask(); private Log log = LogFactory.getLog(ThreadStartUpListenser.class); @Override public void contextDestroyed(ServletContextEvent paramServletContextEvent) { // r.interrupt(); } @Override public void contextInitialized(ServletContextEvent paramServletContextEvent) { //將監控文件類添加為一個觀察者,並啟動一個線程 ObserverListener listen = new ObserverListener(); r.addObserver(listen); new Thread(r).start(); // r.start(); log.info("ImportUserFromFileTask is started!"); } }
2、主體對象:即下面的監控文件新增類WatchFilePathTask ,每次有新文件進來,自動解析該文件,掛掉之後,調用動doBusiness()裏面的notifyObservers()方法,偽代碼如下:
//繼承java內置觀察者模式實現的Observable 類 public class WatchFilePathTask extends Observable implements Runnable { private Log log = LogFactory.getLog(WatchFilePathTask.class); private static final String FILE_PATH = ConfigUtils.getInstance() .getValue("userfile_path"); private WatchService watchService; /** * 此方法一經調用,立馬可以通知觀察者,在本例中是監聽線程 */ public void doBusiness() { if (true) { super.setChanged(); } notifyObservers(); } @Override public void run() { try { //這裏省略監控新增文件的方法 }catch (Exception e) { e.printStackTrace(); doBusiness();// 在拋出異常時調用,通知觀察者,讓其重啟線程 } } }
3、觀察者對象:即上面出現的ObserverListener類,當主題對象的的notifyObservers()方法被調用的時候,就會調用該類的update()方法,偽代碼如下:
//實現java內置觀察者模式實現的Observer接口,並且註冊主題WatchFilePathTask,以便線程掛掉的時候,再重啟這個線程 public class ObserverListener implements Observer { private Log log = LogFactory.getLog(ObserverListener.class); /** * @param o * @param arg */ public void update(Observable o, Object arg) { log.info("WatchFilePathTask掛掉"); WatchFilePathTask run = new WatchFilePathTask(); run.addObserver(this); new Thread(run).start(); log.info("WatchFilePathTask重啟"); } }
關於這個例子更多詳細實現,請查看我的這篇文章:觀察者模式實際應用:監聽線程,意外退出線程後自動重啟
學習本就是一個不斷模仿、練習、再到最後面自己原創的過程。
雖然可能從來不能寫出超越網上通類型同主題博文,但為什麽還是要寫?
於自己而言,博文主要是自己總結。假設自己有觀眾,畢竟講是最好的學(見下圖)。於讀者而言,筆者能在這個過程get到知識點,那就是雙贏了。
當然由於筆者能力有限,或許文中存在描述不正確,歡迎指正、補充!
感謝您的閱讀。如果本文對您有用,那麽請點贊鼓勵。
設計模式就該這麽學:以微信訂閱號來講觀察者模式(第三篇)