1. 程式人生 > >Java 設計模式 觀察者模式

Java 設計模式 觀察者模式

例如 null 可能 truct pri color img cte bstr

  在閻宏博士的《JAVA與模式》一書中開頭是這樣描述觀察者(Observer)模式的:觀察者模式是對象的行為模式,又叫發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。

  

  觀察者模式的結構

  一個軟件系統通常要求在某個對象的狀態變化時,其他的某些對象隨之變化。觀察者模式是滿足這一要求的所有方案中最好的一種。結構圖如下:

  技術分享

  涉及的角色:

  抽象主題(Subject)角色:抽象主題角色又叫做抽象被觀察者(Observable)角色。定義一個可以增加和刪除觀察者對象的接口,每個主題把所有觀察者對象的引用保存在一個集合(例如ArrayList)裏面。

  具體主題(ConcreteSubject)角色:具體主題角色又叫做具體被觀察者(Concrete Observable)角色。在具體主題的狀態變化時,給所有登記過的觀察者發出通知。

  抽象觀察者(Observer)角色:為所有的具體觀察者定義一個更新接口,收到主題的通知時更新具體觀察者。

  具體觀察者(ConcreteObserver)角色:存儲與主題狀態適應的狀態,實現了抽象觀察者角色的更新接口。

  抽象主題角色類

 1 public abstract class Subject {
 2     /**
 3      * 用來保存註冊的觀察者對象
 4      */
 5     private    List<Observer> list = new ArrayList<Observer>();
 6     /**
 7      * 註冊觀察者對象
 8      * @param observer    觀察者對象
 9      */
10     public void attach(Observer observer){
11         
12
list.add(observer); 13 System.out.println("Attached an observer"); 14 } 15 /** 16 * 刪除觀察者對象 17 * @param observer 觀察者對象 18 */ 19 public void detach(Observer observer){ 20 21 list.remove(observer); 22 } 23 /** 24 * 通知所有註冊的觀察者對象 25 */ 26 public void nodifyObservers(String newState){ 27 28 for(Observer observer : list){ 29 observer.update(newState); 30 } 31 } 32 }

  具體主題角色類

 1 public class ConcreteSubject extends Subject{
 2     
 3     private String state;
 4     
 5     public String getState() {
 6         return state;
 7     }
 8 
 9     public void change(String newState){
10         state = newState;
11         System.out.println("主題狀態為:" + state);
12         //狀態發生改變,通知各個觀察者
13         this.nodifyObservers(state);
14     }
15 }

  抽象觀察者角色類

1 public interface Observer {
2     /**
3      * 更新接口
4      * @param state    更新的狀態
5      */
6     public void update(String state);
7 }

  具體觀察者角色類

 1 public class ConcreteObserver implements Observer {
 2     //觀察者的狀態
 3     private String observerState;
 4     
 5     @Override
 6     public void update(String state) {
 7         /**
 8          * 更新觀察者的狀態,使其與目標的狀態保持一致
 9          */
10         observerState = state;
11         System.out.println("狀態為:"+observerState);
12     }
13 
14 }

  客戶端類

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //創建主題對象
 5         ConcreteSubject subject = new ConcreteSubject();
 6         //創建觀察者對象
 7         Observer observer = new ConcreteObserver();
 8         //將觀察者對象登記到主題對象上
 9         subject.attach(observer);
10         //改變主題對象的狀態
11         subject.change("new state");
12     }
13 
14 }

  結果:

  技術分享

  客戶端先創建具體主題類的實例和一個觀察者對象。然後,它調用主題對象的attach方法,將觀察者對象登記到主題對象上,即把它加入到主題對象的集合中。客戶端調用主題對象的change方法後改變了主題對象的狀態。主題對象在狀態變化時,調用父類的notifyObservers方法,通知所有登記過的觀察者對象,更新它們的狀態。

  推模型和拉模型

  觀察者模式分為推模型和拉模型兩種方式。

  推模型:不管觀察者對象是否需要,主題對象都會向觀察者對象推送主題的詳細信息。

  拉模型: 主題對象在通知觀察者對象時,只傳遞少量信息。如果觀察者對象需要更具體的信息,觀察者對象需要主動向主題對象獲取,相當於觀察者對象從主題對象中拉數據。通過update方法把主題對象傳遞給觀察者對象,通過主題對象獲取信息。

  前面的例子是推模型,下面給出一個拉模型的實例:

  拉模型的抽象觀察者類

1 public interface Observer {
2     /**
3      * 更新接口
4      * @param subject 傳入主題對象,方面獲取相應的主題對象的狀態
5      */
6     public void update(Subject subject);
7 }

  拉模型的具體觀察者類

 1 public class ConcreteObserver implements Observer {
 2     //觀察者的狀態
 3     private String observerState;
 4     
 5     @Override
 6     public void update(Subject subject) {
 7         /**
 8          * 更新觀察者的狀態,使其與目標的狀態保持一致
 9          */
10         observerState = ((ConcreteSubject)subject).getState();
11         System.out.println("觀察者狀態為:"+observerState);
12     }
13 
14 }

  拉模型的抽象主題類

 1 public abstract class Subject {
 2     /**
 3      * 用來保存註冊的觀察者對象
 4      */
 5     private    List<Observer> list = new ArrayList<Observer>();
 6     /**
 7      * 註冊觀察者對象
 8      * @param observer    觀察者對象
 9      */
10     public void attach(Observer observer){
11         
12         list.add(observer);
13         System.out.println("Attached an observer");
14     }
15     /**
16      * 刪除觀察者對象
17      * @param observer    觀察者對象
18      */
19     public void detach(Observer observer){
20         
21         list.remove(observer);
22     }
23     /**
24      * 通知所有註冊的觀察者對象
25      */
26     public void nodifyObservers(){
27         
28         for(Observer observer : list){
29             observer.update(this);
30         }
31     }
32 }

  拉模型的具體主題類

 1 public class ConcreteSubject extends Subject{
 2     
 3     private String state;
 4     
 5     public String getState() {
 6         return state;
 7     }
 8 
 9     public void change(String newState){
10         state = newState;
11         System.out.println("主題狀態為:" + state);
12         //狀態發生改變,通知各個觀察者
13         this.nodifyObservers();
14     }
15 }

  兩種模式的比較

  推模型是假設主題對象知道觀察者對象需要的數據;而拉模型是主題對象不知道觀察者對象需要什麽數據。

  推模型可能會使觀察者對象難以復用,因為觀察者對象的update方法是按需要定義形參,可能無法兼顧沒有考慮到的使用情況。當新情況出現時,就可能需要提供新的update方法,或者重新實現觀察者類;而拉模型不會出現這種情況,因為觀察者對象的update方法的形參是主題對象,通過主題對象可以獲取所有信息。

  Java對觀察者模式的支持

  java.util庫提供了一個Observer接口和一個Observable類。

  Observer接口

  只定義了一個update方法。當被觀察者對象的狀態變化時,被觀察者對象的notifyObservers方法就會調用該方法。

1 public interface Observer {
2     void update(Observable o, Object arg);
3 }

  Observable類

  被觀察者類相當於抽象主題類,是java.util.Observable類的子類。有兩個方法對Observable的子類來說很重要:一個是setChanged方法,被調用後會設置一個標記變量,代表被觀察者對象的狀態變化了。第二個是notifyObservers方法,被調用時會調用所有登記過的觀察者對象的update方法,更新觀察者對象。

 1 public class Observable {
 2     private boolean changed = false;
 3     private Vector obs;
 4    
 5     /** Construct an Observable with zero Observers. */
 6 
 7     public Observable() {
 8     obs = new Vector();
 9     }
10 
11     /**
12      * 將一個觀察者添加到觀察者聚集上面
13      */
14     public synchronized void addObserver(Observer o) {
15         if (o == null)
16             throw new NullPointerException();
17     if (!obs.contains(o)) {
18         obs.addElement(o);
19     }
20     }
21 
22     /**
23      * 將一個觀察者從觀察者聚集上刪除
24      */
25     public synchronized void deleteObserver(Observer o) {
26         obs.removeElement(o);
27     }
28 
29     public void notifyObservers() {
30     notifyObservers(null);
31     }
32 
33     /**
34      * 如果本對象有變化(那時hasChanged 方法會返回true)
35      * 調用本方法通知所有登記的觀察者,即調用它們的update()方法
36      * 傳入this和arg作為參數
37      */
38     public void notifyObservers(Object arg) {
39 
40         Object[] arrLocal;
41 
42     synchronized (this) {
43 
44         if (!changed)
45                 return;
46             arrLocal = obs.toArray();
47             clearChanged();
48         }
49 
50         for (int i = arrLocal.length-1; i>=0; i--)
51             ((Observer)arrLocal[i]).update(this, arg);
52     }
53 
54     /**
55      * 將觀察者聚集清空
56      */
57     public synchronized void deleteObservers() {
58     obs.removeAllElements();
59     }
60 
61     /**
62      * 將“已變化”設置為true
63      */
64     protected synchronized void setChanged() {
65     changed = true;
66     }
67 
68     /**
69      * 將“已變化”重置為false
70      */
71     protected synchronized void clearChanged() {
72     changed = false;
73     }
74 
75     /**
76      * 檢測本對象是否已變化
77      */
78     public synchronized boolean hasChanged() {
79     return changed;
80     }
81 
82     /**
83      * Returns the number of observers of this <tt>Observable</tt> object.
84      *
85      * @return  the number of observers of this object.
86      */
87     public synchronized int countObservers() {
88     return obs.size();
89     }
90 }

  如何使用?

  被觀察者類Watched

 1 public class Watched extends Observable{
 2     
 3     private String data = "";
 4     
 5     public String getData() {
 6         return data;
 7     }
 8 
 9     public void setData(String data) {
10         
11         if(!this.data.equals(data)){
12             this.data = data;
13             setChanged();
14         }
15         notifyObservers();
16     }
17     
18     
19 }

  觀察者類Watcher

 1 public class Watcher implements Observer{
 2     
 3     public Watcher(Observable o){
 4         o.addObserver(this);
 5     }
 6     
 7     @Override
 8     public void update(Observable o, Object arg) {
 9         
10         System.out.println("狀態發生改變:" + ((Watched)o).getData());
11     }
12 
13 }

  測試類Test

public class Test {

    public static void main(String[] args) {
        
        //創建被觀察者對象
        Watched watched = new Watched();
        //創建觀察者對象,並將被觀察者對象登記
        Observer watcher = new Watcher(watched);
        //給被觀察者狀態賦值
        watched.setData("start");
        watched.setData("run");
        watched.setData("stop");

    }

}

  先創建Watched和Watcher對象。在創建Watcher對象時,將Watched對象作為參數傳入。然後Watched對象調用setData方法,更改Watched對象的狀態。Watched對象通知登記過的Watcher對象,調用它的update方法。

  參考資料

  《JAVA與模式》之觀察者模式

Java 設計模式 觀察者模式