1. 程式人生 > >設計模式(19)--Observer(觀察者模式)--行為型

設計模式(19)--Observer(觀察者模式)--行為型

直接 pen 創建 方法調用 設計方案 之間 分離 number 運行期

作者QQ:1095737364 QQ群:123300273 歡迎加入!

1.模式定義:

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

2.模式特點:

  觀察者模式的核心是先分清角色、定位好觀察者和被觀察者、他們是多對一的關系。
  實現的關鍵是要建立觀察者和被觀察者之間的聯系、比如在被觀察者類中有個集合是用於存放觀察者的、當被檢測的東西發生改變的時候就要通知所有觀察者。在被觀察者中要提供一些對所有觀察者管理的一些方法.目的是添加或者刪除一些觀察者.這樣才能讓被觀察者及時的通知觀察者關系的狀態已經改變、並且調用觀察者通用的方法將變化傳遞過去。

  在實現觀察者模式,如果JDK的Observable類和一個Observer接口能滿足需求,直接復用即可,無需自己編寫抽象觀察者、抽象主題類;
  但是,java.util.Observable是一個類而不是接口,你必須設計一個類繼承它。如果某個類想同時具有Observable類和另一個超類的行為,由於java不支持多重繼承。所以這個時候就需要自己實現一整套觀察者模式。

3.使用場景:

  (1)當一個對象的改變需要給變其它對象時,而且它不知道具體有多少個對象有待改變時。
  (2)當一個抽象模型有兩個方面,其中一個方面的操作依賴於另一個方面的狀態變化,那麽就可以選用觀察者模式,將這兩者封裝成觀察者和被觀察者,當被觀察者對象變化的時候,依賴於它的觀察者對象也會發生相應的變化。這樣就把抽象模型的這兩個方面分離開了,使得它們可以獨立的改變和復用。

  (3)當更改一個對象的時候,需要同時連帶改變其它的對象,而且不知道究竟應該有多少對象需要被連帶改變,這種情況可以選用觀察者模式,被更改的那一個對象很明顯就相當於是被觀察者對象,而需要連帶修改的多個其它對象,就作為多個觀察者對象了。
  (4)當一個對象必須通知其它的對象,但是你又希望這個對象和其它被它通知的對象是松散耦合的,也就是說這個對象其實不想知道具體被通知的對象,這種情況可以選用觀察者模式,這個對象就相當於是被觀察者對象,而被它通知的對象就是觀察者對象了。

4.模式實現:

  一個軟件系統裏面包含了各種對象,就像一片欣欣向榮的森林充滿了各種生物一樣。在一片森林中,各種生物彼此依賴和約束,形成一個個生物鏈。一種生物的狀態變化會造成其他一些生物的相應行動,每一個生物都處於別的生物的互動之中。

  同樣,一個軟件系統常常要求在某一個對象的狀態發生變化的時候,某些其他的對象做出相應的改變。做到這一點的設計方案有很多,但是為了使系統能夠易於復用,應該選擇低耦合度的設計方案。減少對象之間的耦合有利於系統的復用,但是同時設計師需要使這些低耦合度的對象之間能夠維持行動的協調一致,保證高度的協作。觀察者模式是滿足這一要求的各種設計方案中最重要的一種。
  下面以一個簡單的示意性實現為例,討論觀察者模式的結構。

技術分享

觀察者模式所涉及的角色有:

  (1)抽象主題(Subject)角色:

    抽象主題角色把所有對觀察者對象的引用保存在一個聚集(比如ArrayList對象)裏,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,抽象主題角色又叫做抽象被觀察者(Observable)角色。

public abstract class Subject {
    /**
     * 用來保存註冊的觀察者對象
     */
    private    List<Observer> list = new ArrayList<Observer>();
    /**
     * 註冊觀察者對象
     * @param observer    觀察者對象
     */
    public void attach(Observer observer){
        
        list.add(observer);
        System.out.println("Attached an observer");
    }
    /**
     * 刪除觀察者對象
     * @param observer    觀察者對象
     */
    public void detach(Observer observer){
        
        list.remove(observer);
    }
    /**
     * 通知所有註冊的觀察者對象
     */
    public void nodifyObservers(String newState){
        
        for(Observer observer : list){
            observer.update(newState);
        }
    }
}

  (2)具體主題(ConcreteSubject)角色:

    將有關狀態存入具體觀察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。

public class ConcreteSubject extends Subject{    
    private String state;
    public String getState() {
        return state;
    }
    public void change(String newState){
        state = newState;
        System.out.println("主題狀態為:" + state);
        //狀態發生改變,通知各個觀察者
        this.nodifyObservers(state);
    }
}

  (3)抽象觀察者(Observer)角色:

    為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己,這個接口叫做更新接口。

public interface Observer {
    /**
     * 更新接口
     * @param state    更新的狀態
     */
    public void update(String state);
}

  (4)具體觀察者(ConcreteObserver)角色:

    存儲與主題的狀態自恰的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態 像協調。如果需要,具體觀察者角色可以保持一個指向具體主題對象的引用。

public class ConcreteObserver implements Observer {
    //觀察者的狀態
    private String observerState;
    @Override
    public void update(String state) {
        /**
         * 更新觀察者的狀態,使其與目標的狀態保持一致
         */
        observerState = state;
        System.out.println("狀態為:"+observerState);
    }
}

  (5)客戶端

public class Client {
    public static void main(String[] args) {
        //創建主題對象
        ConcreteSubject subject = new ConcreteSubject();
        //創建觀察者對象
        Observer observer = new ConcreteObserver();
        //將觀察者對象登記到主題對象上
        subject.attach(observer);
        //改變主題對象的狀態
        subject.change("new state");
    }
}

運行結果如下

技術分享

  在運行時,這個客戶端首先創建了具體主題類的實例,以及一個觀察者對象。然後,它調用主題對象的attach()方法,將這個觀察者對象向主題對象登記,也就是將它加入到主題對象的聚集中去。
  這時,客戶端調用主題的change()方法,改變了主題對象的內部狀態。主題對象在狀態發生變化時,調用超類的notifyObservers()方法,通知所有登記過的觀察者對象。

推模型和拉模型

  在觀察者模式中,又分為推模型和拉模型兩種方式。
    推模型: 主題對象向觀察者推送主題的詳細信息,不管觀察者是否需要,推送的信息通常是主題對象的全部或部分數據。
    拉模型: 主題對象在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到主題對象中獲取,相當於是觀察者從主題對象中拉數據。一般這種模型的實現中,會把主題對象自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取數據的時候,就可以通過這個引用來獲取了。
  根據上面的描述,發現前面的例子就是典型的推模型,下面給出一個拉模型的實例。  

    [1]拉模型的抽象觀察者類:拉模型通常都是把主題對象當做參數傳遞。

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

    [2]拉模型的具體觀察者類

public class ConcreteObserver implements Observer {
    //觀察者的狀態
    private String observerState;
    
    @Override
    public void update(Subject subject) {
        /**
         * 更新觀察者的狀態,使其與目標的狀態保持一致
         */
        observerState = ((ConcreteSubject)subject).getState();
        System.out.println("觀察者狀態為:"+observerState);
    }

}  

    [3]拉模型的抽象主題類

      拉模型的抽象主題類主要的改變是nodifyObservers()方法。在循環通知觀察者的時候,也就是循環調用觀察者的update()方法的時候,傳入的參數不同了。

public abstract class Subject {
    /**
     * 用來保存註冊的觀察者對象
     */
    private    List<Observer> list = new ArrayList<Observer>();
    /**
     * 註冊觀察者對象
     * @param observer    觀察者對象
     */
    public void attach(Observer observer){
        
        list.add(observer);
        System.out.println("Attached an observer");
    }
    /**
     * 刪除觀察者對象
     * @param observer    觀察者對象
     */
    public void detach(Observer observer){
        
        list.remove(observer);
    }
    /**
     * 通知所有註冊的觀察者對象
     */
    public void nodifyObservers(){
        
        for(Observer observer : list){
            observer.update(this);
        }
    }
} 

    [4]拉模型的具體主題類

      跟推模型相比,有一點變化,就是調用通知觀察者的方法的時候,不需要傳入參數了。

public class ConcreteSubject extends Subject{    
    private String state;    
    public String getState() {
        return state;
    }
    public void change(String newState){
        state = newState;
        System.out.println("主題狀態為:" + state);
        //狀態發生改變,通知各個觀察者
        this.nodifyObservers();
    }
}

兩種模式的比較

  推模型是假定主題對象知道觀察者需要的數據;而拉模型是主題對象不知道觀察者具體需要什麽數據,沒有辦法的情況下,幹脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。
  推模型可能會使得觀察者對象難以復用,因為觀察者的update()方法是按需要定義的參數,可能無法兼顧沒有考慮到的使用情況。這就意味著出現新情況的時候,就可能提供新的update()方法,或者是幹脆重新實現觀察者;而拉模型就不會造成這樣的情況,因為拉模型下,update()方法的參數是主題對象本身,這基本上是主題對象能傳遞的最大數據集合了,基本上可以適應各種情況的需要。

JAVA提供的對觀察者模式的支持  

    在JAVA語言的java.util庫裏面,提供了一個Observable類以及一個Observer接口,構成JAVA語言對觀察者模式的支持。

  Observer接口

    這個接口只定義了一個方法,即update()方法,當被觀察者對象的狀態發生變化時,被觀察者對象的notifyObservers()方法就會調用這一方法。

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

  Observable類

    被觀察者類都是java.util.Observable類的子類。java.util.Observable提供公開的方法支持觀察者對象,這些方法中有兩個對Observable的子類非常重要:一個是setChanged(),另一個是notifyObservers()。第一方法setChanged()被調用之後會設置一個內部標記變量,代表被觀察者對象的狀態發生了變化。第二個是notifyObservers(),這個方法被調用時,會調用所有登記過的觀察者對象的update()方法,使這些觀察者對象可以更新自己。

public class Observable {
    private boolean changed = false;
    private Vector obs;
    /** Construct an Observable with zero Observers. */
    public Observable() {
    obs = new Vector();
    }
    /**
     * 將一個觀察者添加到觀察者聚集上面
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
    if (!obs.contains(o)) {
        obs.addElement(o);
    }
    }
    /**
     * 將一個觀察者從觀察者聚集上刪除
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    public void notifyObservers() {
    notifyObservers(null);
    }
    /**
     * 如果本對象有變化(那時hasChanged 方法會返回true)
     * 調用本方法通知所有登記的觀察者,即調用它們的update()方法
     * 傳入this和arg作為參數
     */
    public void notifyObservers(Object arg) {
        Object[] arrLocal;
    synchronized (this) {
        if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    /**
     * 將觀察者聚集清空
     */
    public synchronized void deleteObservers() {
    obs.removeAllElements();
    }

    /**
     * 將“已變化”設置為true
     */
    protected synchronized void setChanged() {
    changed = true;
    }

    /**
     * 將“已變化”重置為false
     */
    protected synchronized void clearChanged() {
    changed = false;
    }
    /**
     * 檢測本對象是否已變化
     */
    public synchronized boolean hasChanged() {
    return changed;
    }
    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     *
     * @return  the number of observers of this object.
     */
    public synchronized int countObservers() {
    return obs.size();
    }
}

   這個類代表一個被觀察者對象,有時稱之為主題對象。一個被觀察者對象可以有數個觀察者對象,每個觀察者對象都是實現Observer接口的對象。在被觀察者發生變化時,會調用Observable的notifyObservers()方法,此方法調用所有的具體觀察者的update()方法,從而使所有的觀察者都被通知更新自己。

怎樣使用JAVA對觀察者模式的支持
    這裏給出一個非常簡單的例子,說明怎樣使用JAVA所提供的對觀察者模式的支持。在這個例子中,被觀察對象叫做Watched;而觀察者對象叫做Watcher。Watched對象繼承自java.util.Observable類;而Watcher對象實現了java.util.Observer接口。另外有一個Test類扮演客戶端角色。
源代碼

    [1]被觀察者Watched類源代碼

public class Watched extends Observable{    
    private String data = "";   
    public String getData() {
        return data;
    }
    public void setData(String data) {
        
        if(!this.data.equals(data)){
            this.data = data;
            setChanged();
        }
        notifyObservers();
    }
    
    
}

    [2]觀察者類源代碼

public class Watcher implements Observer{
    
    public Watcher(Observable o){
        o.addObserver(this);
    }
    @Override
    public void update(Observable o, Object arg) {
        
        System.out.println("狀態發生改變:" + ((Watched)o).getData());
    }
} 

    [3]測試類源代碼

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");
    }
}

    Test對象首先創建了Watched和Watcher對象。在創建Watcher對象時,將Watched對象作為參數傳入;然後Test對象調用Watched對象的setData()方法,觸發Watched對象的內部狀態變化;Watched對象進而通知實現登記過的Watcher對象,也就是調用它的update()方法。

5.優缺點:

  (1)觀察者模式的優點


    [1](觀察者和被觀察者是抽象耦合的)原本被觀察者對象在狀態改變的時候,需要直接調用所有的觀察者對象,但是抽象出觀察者接口過後,被觀察者和觀察者就只是在抽象層面上耦合了,也就是說被觀察者只是知道觀察者接口,並不知道具體的觀察者的類,從而實現被觀察者類和具體的觀察者類之間解耦。
    [2](實現了動態聯動)聯動就是做一個操作會引起其它相關的操作。由於觀察者模式對觀察者註冊實行管理,那就可以在運行期間,通過動態的控制註冊的觀察者,來控制某個動作的聯動範圍,從而實現動態聯動。
    [3](支持廣播通信)由於被觀察者發送通知給觀察者是面向所有註冊的觀察者,所以每次被觀察者通知的信息就要對所有註冊的觀察者進行廣播。當然,也可以通過在被觀察者上添加新的功能來限制廣播的範圍。
    [4]觀察者模式解除了主題和具體觀察者的耦合,讓耦合的雙方都依賴於抽象,而不是依賴具體。從而使得各自的變化都不會影響另一邊的變化。

  (2)觀察者模式的缺點:

    [1]如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
    [2]如果在觀察者和被觀察者之間有循環依賴的話,被觀察者會觸發它們形成循環調用,可能導致系統崩潰。
    [3]觀察者模式沒有相應的機制讓觀察者知道被觀察者對象是怎麽發生變化的,而僅僅只是知道被觀察者發生了變化。
    [4] 依賴關系並未完全解除,抽象通知者依舊依賴抽象的觀察者。

設計模式(19)--Observer(觀察者模式)--行為型