工作的時候看到前人寫的代碼中涉及到觀察者模式,之前一直也想學以下這種模式,所以這次就對觀察者模式的學習做下記錄。
觀察者模式又稱發布-訂閱模式,說的通俗點,舉個例子:我和朋友打dota,我玩lion,朋友玩小小,我對敵人放了技能妖術,然后我叫朋友放技能投擲,他放完了叫我放技能穿刺,我放完了地刺再叫他放技能山崩,他放完了以后再叫我放大招。這個例子里面多次用到了觀察者模式的思路。我放完一個技能,然后通知我的朋友,這個過程中我就是被觀察者,我朋友就是觀察者,我釋放玩技能,叫我朋友的動作就是通知;同理我朋友放完一個技能,然后通知我,這個時候他就是被觀察者,我就是觀察者。
觀察者的好處是不需要一直監控着某個對象。假如有100個線程在同一個資源被阻塞,如果不使用通知,那么這100個線程是不是要時刻監控着這個資源呢?每過一段時間查看一次這個資源是不是可以得到,這種監控對於計算機來說是種極大的浪費不是嗎?現在的I/O資源,當寫入成功或者讀取成功的時候都會發出一個notify通知。來通知等待這個資源的線程。
java中自己本身有實現一個標准的觀察者模式,Observable(被觀察對象的抽象),Observer(觀察者的接口抽象),使用的話,被觀察對象只需要繼承Observable,觀者者對象只需要實現Observer接口即可。觀察者與被觀察對象創建完以后只需要把觀察者添加到被觀察者對象中的vector容器即可。
下面看下源代碼,Observer:
1 package java.util; 2 3 public interface Observer { 4 void update(Observable o, Object arg); 5 }
Observer中只有一個update接口,有2個參數,第一個Observable就是被觀察的地向,第二個Object對象,可以傳遞update中需要使用到的信息。
Observable:
1 package java.util; 2 3 public class Observable { 4 private boolean changed = false; 5 private Vector 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 27 public void notifyObservers(Object arg) { 28 29 Object[] arrLocal; 30 31 synchronized (this) { 32 if (!changed) 33 return; 34 arrLocal = obs.toArray(); 35 clearChanged(); 36 } 37 38 for (int i = arrLocal.length-1; i>=0; i--) 39 ((Observer)arrLocal[i]).update(this, arg); 40 } 41 42 public synchronized void deleteObservers() { 43 obs.removeAllElements(); 44 } 45 46 protected synchronized void setChanged() { 47 changed = true; 48 } 49 50 protected synchronized void clearChanged() { 51 changed = false; 52 } 53 54 public synchronized boolean hasChanged() { 55 return changed; 56 } 57 58 public synchronized int countObservers() { 59 return obs.size(); 60 } 61 }
Observable中定義了一個Vector容器以及一個boolean類型的changed變量,vector容器用於存放觀察這個對象的所有的觀察者,changed用於表示該對象是否改變。
這個類中的所有的方法都加上了synchronized(除了norifyObservers,內部有同步機制),說明這個Observable是線程安全的。主要分析一下notifyObservers方法。notifyObservers方法中獲取vector中的元素的時候用了一個同步鎖,鎖的對象為當前的Observable類。為什么不像其他方法那樣加上synchronied關鍵字呢?如果像其他方法那樣的寫法,那么Vector中的所有觀察者的更新操作就必須串行更新,如果這些更新操作需要耗費很多時間,那么程序就會出現無響應的情況,所以更新操作並不需要同步。
那為什么要像源代碼中那么寫呢?原因就是使提取Vector中的觀察者的操作得到同步,並且維護changed的狀態。假如我們不要加這個synchronied同步代碼,第一種情況,存在兩個線程A和B,如果線程B操作被觀察對象拷貝了Vector中的所有觀察者,此時時間片切換,線程A操作被觀察對象添加一個觀察者,然后B線程繼續執行更新操作,那么線程A添加的觀察者在線程B的此次更新操作中並不會得到更新;第二種情況,線程B操作被觀察者對象拷貝了Vector中的所有的觀察者,此時時間片切換,線程A操作被觀察者對象刪除一個觀察者(線程B中拷貝的並沒有被刪除),線程B繼續執行更新操作,那么在線程A中刪除的觀察者此次在B中依然會執行更新操作。以上2點所以需要對拷貝操作以及changed的狀態進行同步,更新操作不需要同步,因為操作時間很長而且每個方法執行的時候都會創建一個棧幀,不存在資源共享問題,所以不會有競爭。
看完了Java中提供的觀察者模式相關的源代碼,下面是我實現的一個簡單的例子。
MyObserved(被觀察者對象):
1 package froest.obserpackege; 2 3 import java.util.Observable; 4 5 public class MyObserved extends Observable { 6 private int cnt = 1; 7 public int getCnt() { 8 return cnt; 9 } 10 public void setCnt(int a) { 11 this.cnt = a; 12 } 13 public static void main(String[] args) { 14 // TODO Auto-generated method stub 15 MyObserved obj1 = new MyObserved(); 16 obj1.addObserver(new MyObserver1()); 17 obj1.addObserver(new MyObserver2()); 18 System.out.println(obj1.getCnt()); 19 obj1.setCnt(3); 20 obj1.setChanged(); 21 obj1.notifyObservers(); 22 System.out.println(obj1.getCnt()); 23 } 24 }
MyObserver1(第一個觀察者):
1 package froest.obserpackege; 2 3 import java.util.Observable; 4 import java.util.Observer; 5 6 public class MyObserver1 implements Observer { 7 8 @Override 9 public void update(Observable o, Object arg) { 10 System.out.println(((MyObserved)o).getCnt()); 11 System.out.println("update"); 12 ((MyObserved)o).setCnt(5); 13 } 14 15 }
MyObserver2(第二個觀察者):
1 package froest.obserpackege; 2 3 import java.util.Observable; 4 import java.util.Observer; 5 6 public class MyObserver2 implements Observer { 7 8 @Override 9 public void update(Observable o, Object arg) { 10 System.out.println("Observer2"); 11 } 12 13 }
被觀察的對象更新了狀態以后一定要調用setChanged方法,設置changed為true,在調用notifyObservers方法的時候會根據這changed值來判斷是否需要更新。
JDK支持的觀察者模式,為了能夠把具體的被觀察者和觀察者分離開來,抽象了具體的被觀察者以及觀察者,減少了兩者之間的耦合性,但是有一點不足的是被觀察者在調用update方法的時候,會把被觀察者對象的this引用傳遞給觀察者,那么在觀察者這邊可以實現對被觀察者的狀態的修改,如果是發布-訂閱模式的話,這是不應該出現的,訂閱方(也就是觀察者)應該只有查看功能,根據被觀察者的狀態更新自己的行為,而不應該去修改觀察者,所以JDK支持的觀察者模式並不完全適用於發布-訂閱模式,需要對update稍作修改,使得被觀察者對於觀察者是透明的。
