設計模式-觀察者模式
- 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介面,它是如何實現的呢?且看下回分析...