設計模式-觀察者模式

  • 2019 年 10 月 7 日
  • 筆記

前言

        整個9月份基本是在花式加班中疲勞度過的,工作中進步不少,自學進度卻放慢了。十一長假,處理完家裡的事情後提前一天來了上海,收攏一下思緒,準備迎接下一階段的工作、學習。不知不覺,2019年只剩下了不到三個月,來自時間的壓迫感無時無刻不在,需要抓緊最後的機會,利用好這三四個月的時間。

        閑話少敘,這一次我打算將觀察者設計模式梳理一下,從JDK中的設計,到Spring中的應用,都會涉及到。心得以及感悟都是一家之言,如有不恰當之處,還望各位道友指正!

一、結合案例分析java中的觀察者模式

        首先看一下java中已經定義好了的觀察者類(Observer)、被觀察者類(Observable)的結構:

1 // 觀察者類  2 public interface Observer {  3     // 此方法用於定義觀察者觀察到變化後發生的行為  4     // 第一個參數是被觀察者;第二個參數是一個可變對象,方便動態傳遞某些資訊  5     void update(Observable o, Object arg);  6 }
 1 public class Observable {   2     // 變動標識,用於判斷被觀察者是否有變化   3     private boolean changed = false;   4     // 存放觀察者   5     private Vector<Observer> obs;   6   7     public Observable() {   8         obs = new Vector<>();   9     }  10     11     public synchronized void addObserver(Observer o) {  12         if (o == null)  13             throw new NullPointerException();  14         if (!obs.contains(o)) {  15             obs.addElement(o);  16         }  17     }  18  19     public synchronized void deleteObserver(Observer o) {  20         obs.removeElement(o);  21     }  22  23     public void notifyObservers() {  24         notifyObservers(null);  25     }  26     // 通知被觀察者發生了變化,循環調用update方法 ☆  27     public void notifyObservers(Object arg) {  28         Object[] arrLocal;  29  30         synchronized (this) {  31             if (!changed)  32                 return;  33             arrLocal = obs.toArray();  34             clearChanged();  35         }  36  37         for (int i = arrLocal.length-1; i>=0; i--)  38             ((Observer)arrLocal[i]).update(this, arg);  39     }  40  41     public synchronized void deleteObservers() {  42         obs.removeAllElements();  43     }  44     // 將改變狀態設置成已改變  45     protected synchronized void setChanged() {  46         changed = true;  47     }  48  49     protected synchronized void clearChanged() {  50         changed = false;  51     }  52  53     public synchronized boolean hasChanged() {  54         return changed;  55     }  56  57     public synchronized int countObservers() {  58         return obs.size();  59     }  60 }

        可見被觀察者類中維護了一個觀察者的列表,在發生變化通知觀察者時是循環列表,調用每個觀察者的update方法,具體每個觀察者是如何做的,取決於其update方法。

        知道了java中的觀察者和被觀察者類,我們要如何使用呢?且看下面的例子。

        十一回來的路上看了好幾篇半佛仙人的文章,略有感悟,於是此次就用仙人的案例作為主體進行設計。仙人是一個風控出身的"社會工程學專家",熱衷於寫文章揭露社會上的沙雕事件、違法事件。所以此處觀察者就是仙人,而被觀察者則是違法事件,下面是定義出來的兩個類:

 1 /**   2  * 仙人是觀察者,實現Observer觀察者介面   3  */   4 public class CelestialBeing implements Observer {   5   6     @Override   7     public void update(Observable o, Object arg) {   8         writeToPen();   9     }  10     // 寫文章揭露(也就是噴)  11     private void writeToPen(){  12         System.out.println("寫文章批鬥、吊打");  13     }  14 }
 1 /**   2  * 違法事件是被觀察者,一旦出現變化,會被半佛仙人觀察到   3  */   4 public class IllegalThing extends Observable {   5   6     public void change() {   7         System.out.println("出現了違法事件");   8         super.setChanged();   9     }  10 }

下面是測試類:

 1 public class TestClient {   2   3     public static void main(String[] args) {   4         IllegalThing illegalThing = new IllegalThing();   5         /**   6          * 觀察者模式的關鍵點:被觀察者持有觀察者   7          */   8         illegalThing.addObserver(new CelestialBeing());   9         // 有改變  10         illegalThing.change();  11         // 通知所有觀察者  12         illegalThing.notifyObservers();  13  14     }  15 }

運行結果:

1 出現了違法事件  2 寫文章批鬥、吊打

        可以看到,由於被觀察者類Observable自身已經維護好了觀察者列表,所以我們的被觀察者類不需要做太多的事情,只需要將setChanged方法暴露出去即可。觀察者模式的關鍵,就是上面注釋中提到的:被觀察者持有觀察者列表。只要注意了這一點,相信大家在實際場景中使用時便不會用錯。

二、看看觀察者模式在Spring中的應用

1、在Spring容器的某個階段觸發事件

        如果我想在Spring容器refresh之後觸發某些特定邏輯,那麼定義一個這樣的類就可以:

1 @Component  2 public class MySpringListener implements ApplicationListener<ContextRefreshedEvent> {  3  4     @Override  5     public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {  6         System.out.println("MySpringObserver執行了");  7         // 執行自定義邏輯  8     }  9 }

        能如此方便的做到,主要是因為Spring幫我們創建好了refresh的事件,在Spring容器過程中類似的事件有以下幾個:

 

 

 2、自定義的事件觸發

        如果我們想在某些特定的時機觸發一個自定義的事件,比如在發郵件時觸發一個事件監聽,那麼在Spring中要怎麼做呢?

首先定義一個郵件事件:

1 public class MySpringEmailEvent extends ApplicationEvent {  2  3     public MySpringEmailEvent(Object source) {  4         super(source);  5     }  6 }
1 @Component  2 public class MySpringEmailListener implements ApplicationListener<MySpringEmailEvent> {  3  4     @Override  5     public void onApplicationEvent(MySpringEmailEvent mySpringEmailEvent) {  6         System.out.println("MySpringEmailListener執行了");  7         // 執行自定義邏輯  8     }  9 }
1 @Component  2 public class MyEventTrigger {  3     @Autowired  4     private ApplicationContext applicationContext;  5     // 觸發事件  6     public void sendEmail () {  7         applicationContext.publishEvent(new MySpringEmailEvent(applicationContext)); 
8 }
9 }

測試類:

1 public class SpringClient {  2     public static void main(String[] args) {  3         AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);  4         MyEventTrigger bean = applicationContext.getBean(MyEventTrigger.class);  5         bean.sendEmail();  6     }  7 }

執行結果:

1 MySpringEmailListener執行了

        看到這裡,相信很多道友會有疑問,Spring是如何將事件和listener關聯在一起的呢?其實內部是通過ApplicationEventMulticaster介面,它是如何實現的呢?且看下回分析...

Tags: