前言:繼續《設計模式就該這麼學》系列文章,今天以當前比較火的微信訂閱號給你們介紹應用得比較多的一種設計模式——觀察者模式,以後再來介紹java拉模型方式的內置設計模式實現,最後附帶一個項目實際觀察者應用的例子!html
《設計模式就該這麼學》系列文章:java
設計模式就該這麼學:爲何要學設計模式?(開篇漫談)react
設計模式就該這麼學:要走心才能遵循設計模式五大原則(第二篇)編程
以《Head First 設計模式》這本書中的定義:微信
觀察者模式:它定義了對象之間的一(Subject)對多(Observer)的依賴,這樣一來,當一個對象(Subject)改變時,它的全部的依賴者都會收到通知並自動更新。 編程語言
首先看下觀察者模式的類圖ide
看了上面定義及類圖好像不太容易理解,微信訂閱號我相信你們都不陌生,接下來就微信訂閱號的例子來介紹下觀察者模式。首先看下面一張圖:post
如上圖所示,微信訂閱號就是咱們的主題,用戶就是觀察者。他們在這個過程當中扮演的角色及做用分別是:
接下來讓咱們經過一段示例,三位同事zhangsai、liyong、liujing訂閱人民日報訂閱號爲例來介紹觀察者模式(Obsever),代碼以下:
//一、人民日報接口 public interface PeoplesDaily { //添加訂閱者 void RegisterObserver(Observer observer); //取消訂閱 void RemoveObserver(Observer observer); //發送人民日報 void notifyObservers(); } //二、訂閱者接口 public interface Observer { //有新的人民日報了就會被執行通知 void update(); } //三、人民日報 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(); } } } //四、訂閱者 public class subHuman implements Observer { //訂閱者的名字 private string name; public subHuman(string f_name) { name = f_name; } //通知訂閱者有新人民日報推送消息了 public void update() { system.out.println(p_name + "!! 有新的人民日報消息了,請查收!"); } } //五、測試開始訂閱,和調用了 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(); }
測試結果:
張賽!! 有新的人民日報消息了,請查收! 李勇!! 有新的人民日報消息了,請查收! 劉晶!! 有新的人民日報消息了,請查收! ---------------發完人民日報了------------------ 張賽!! 有新的人民日報消息了,請查收! 李勇!! 有新的人民日報消息了,請查收!
在觀察者模式中,又分爲推模型和拉模型兩種方式。
而它們的區別在於:
「推」的好處包括:
一、高效。若是沒有更新發生,不會有任何更新消息推送的動做,即每次消息推送都發生在確確實實的更新事件以後,因此這種推送是有意義的。
二、實時。事件發生後的第一時間便可觸發通知操做。
「拉」的好處包括:
一、若是觀察者衆多,那麼主題要維護訂閱者的列表臃腫,把訂閱關係解脫到Observer去完成,何時要本身去拉數據就行了。
二、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目錄,我須要實現新上傳的文件作一個自動檢測,每次只要有文件新增,自動解析新增文件內容入庫,而且要保證該功能的穩定性!!
實現思路:
一、監聽器初始化建立:首先在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!"); } }
二、主體對象:即下面的監控文件新增類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();// 在拋出異常時調用,通知觀察者,讓其重啓線程 } } }
三、觀察者對象:即上面出現的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到知識點,那就是共贏了。
固然因爲筆者能力有限,或許文中存在描述不正確,歡迎指正、補充!
感謝您的閱讀。若是本文對您有用,那麼請點贊鼓勵。