1.定義 定義對象間一種一對多的依賴關係,使得當每一個對象改變狀態,則所有依賴於它的對象都會得到通知並自動更新。 2.類圖 3.代碼示例 我們定義一個場景:熱水壺在燒開水,小孩和媽媽都關註燒開水的過程,各自有其處理方法。用while死迴圈一直輪詢雖然可以實現這樣的場景,但性能上讓人無法接受。 為方便 ...
1.定義
定義對象間一種一對多的依賴關係,使得當每一個對象改變狀態,則所有依賴於它的對象都會得到通知並自動更新。
2.類圖
3.代碼示例
我們定義一個場景:熱水壺在燒開水,小孩和媽媽都關註燒開水的過程,各自有其處理方法。用while死迴圈一直輪詢雖然可以實現這樣的場景,但性能上讓人無法接受。
為方便大家copy源碼放在本機測試,我將代碼寫進一個java類中,其它參與類都是非public的。
1 package com.zhaoyangwoo.observer; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * Created by john on 16/8/3. 8 */ 9 //定義被觀察者介面 10 interface Subject { 11 12 void attachObv(Observer o); 13 14 void detachObv(Observer o); 15 16 void notifyObvs(); 17 } 18 19 //定義觀察者介面 20 interface Observer { 21 void update(Subject subject); 22 } 23 24 //定義具體被觀察者 25 public class WaterHeater implements Subject { 26 27 //存儲觀察者 28 private static List<Observer> observerList = new ArrayList<>(); 29 30 public int getTemperature() { 31 return temperature; 32 } 33 34 public void setTemperature(int temperature) { 35 this.temperature = temperature; 36 } 37 38 private int temperature = 0; 39 40 @Override 41 public void attachObv(Observer o) { 42 observerList.add(o); 43 } 44 45 @Override 46 public void detachObv(Observer o) { 47 observerList.remove(o); 48 49 } 50 51 @Override 52 public void notifyObvs() { 53 observerList.forEach(r -> r.update(this)); 54 } 55 56 //模擬燒開水的過程 57 public void heat() { 58 temperature = 0; 59 for (int i = 0; i < 100; i++) { 60 temperature++; 61 this.notifyObvs(); 62 try { 63 //停100ms 64 Thread.sleep(100); 65 } catch (InterruptedException e) { 66 e.printStackTrace(); 67 } 68 } 69 } 70 71 72 //場景類 73 public static void main(String[] args) { 74 WaterHeater waterHeater = new WaterHeater(); 75 Mother mother = new Mother(); 76 Child child = new Child(); 77 waterHeater.attachObv(mother); 78 waterHeater.attachObv(child); 79 waterHeater.heat(); 80 waterHeater.detachObv(child); 81 waterHeater.heat(); 82 System.out.println("燒水結束"); 83 } 84 } 85 86 //具體觀察者 87 class Child implements Observer { 88 89 @Override 90 public void update(Subject subject) { 91 WaterHeater waterHeater = (WaterHeater) subject; 92 System.out.println("現在的水溫是" + waterHeater.getTemperature() + "度,人家還是個寶寶,跟我沒關係"); 93 } 94 } 95 96 //具體觀察者 97 class Mother implements Observer { 98 99 @Override 100 public void update(Subject subject) { 101 WaterHeater waterHeater = (WaterHeater) subject; 102 if (waterHeater.getTemperature() > 99) { 103 System.out.println("現在的水溫是" + waterHeater.getTemperature() + "度,水燒開了,我要把水裝進熱水瓶"); 104 } 105 } 106 }
4.應用場景舉例
- 一個類的狀態變化需要通知其他類知曉,並且這些其他類是可以動態配置的
- 可以實現訂閱/發佈模型
5.JDK源碼中的模式實現
JDK原生對觀察者模式支持。通過java.util.Observable和java.util.Observer兩個類,很容易實現觀察者模式。通過閱讀其源碼,可以發現原理都是一樣的。當然源碼里的實現是線程安全的。我們用這兩個類重寫我們的場景:
1 // jdk版本的被觀察者,不需要自己實現調用通知/註冊之類的操作 2 class WaterHeaterJava extends Observable { 3 public int getTemperature() { 4 return temperature; 5 } 6 7 public void setTemperature(int temperature) { 8 this.temperature = temperature; 9 } 10 11 private int temperature = 0; 12 13 public void heat() { 14 temperature = 0; 15 for (int i = 0; i < 100; i++) { 16 temperature++; 17 //這裡一定要註意,如果要notifyObservers生效,一定要調用setChanged告知已經發生了change,可以通知觀察者了.否則notifyObservers不工作 18 super.setChanged(); 19 super.notifyObservers(); 20 try { 21 Thread.sleep(100); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 } 26 } 27 28 //場景類 29 public static void main(String[] args) { 30 WaterHeaterJava waterHeater = new WaterHeaterJava(); 31 MotherJava mother = new MotherJava(); 32 ChildJava child = new ChildJava(); 33 waterHeater.addObserver(mother); 34 waterHeater.addObserver(child); 35 waterHeater.heat(); 36 waterHeater.deleteObserver(child); 37 waterHeater.heat(); 38 System.out.println("Java版燒水結束"); 39 } 40 } 41 42 class ChildJava implements java.util.Observer { 43 44 @Override 45 public void update(Observable o, Object arg) { 46 WaterHeaterJava waterHeater = (WaterHeaterJava) o; 47 System.out.println("現在的水溫是" + waterHeater.getTemperature() + "度,人家還是個寶寶,跟我沒關係"); 48 } 49 } 50 51 class MotherJava implements java.util.Observer { 52 53 @Override 54 public void update(Observable o, Object arg) { 55 WaterHeaterJava waterHeater = (WaterHeaterJava) o; 56 if (waterHeater.getTemperature() > 99) { 57 System.out.println("現在的水溫是" + waterHeater.getTemperature() + "度,水燒開了,我要把水裝進熱水瓶"); 58 } 59 } 60 }View Code
關於setChanged,看看它的源碼就明白了
1 public void notifyObservers(Object arg) { 2 /* 3 * a temporary array buffer, used as a snapshot of the state of 4 * current Observers. 5 */ 6 Object[] arrLocal; 7 8 synchronized (this) { 9 /* We don't want the Observer doing callbacks into 10 * arbitrary code while holding its own Monitor. 11 * The code where we extract each Observable from 12 * the Vector and store the state of the Observer 13 * needs synchronization, but notifying observers 14 * does not (should not). The worst result of any 15 * potential race-condition here is that: 16 * 1) a newly-added Observer will miss a 17 * notification in progress 18 * 2) a recently unregistered Observer will be 19 * wrongly notified when it doesn't care 20 */ 21 22 //看這裡 23 if (!changed) 24 return; 25 arrLocal = obs.toArray(); 26 clearChanged(); 27 } 28 29 for (int i = arrLocal.length-1; i>=0; i--) 30 ((Observer)arrLocal[i]).update(this, arg); 31 }View Code
6.思考
思考如下兩個問題
- 性能問題
如果註冊的觀察者較多,或者觀察者處理能力弱、耗時長,那麼很可能出現性能問題,畢竟只是簡單的遍歷調用。
- 訂閱/發佈
當然,對於訂閱/發佈模型的支持有更好的開源框架,各種mq實現了這種模型,並且是非同步架構,性能也有保障。
- 推或拉
大家都喜歡說推模式和拉模式。其實本質上來說都是推模式,這也是使用觀察者模式帶來的好處。拉模式只不過推了一個引用,可以通過這個引用拿到更多的信息而已。
7.參考
1.《JAVA與模式》之觀察者模式