設計模式(3):行爲型-觀察者模式(Observer)

設計模式(Design pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的;設計模式使代碼編制真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。

設計模式分爲三種類型,共23種。
創建型模式(5):單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式。
結構型模式(7):適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
行爲型模式(11):(父子類)策略模式、模版方法模式,(兩個類)觀察者模式、迭代器模式、職責鏈模式、命令模式,(類的狀態)狀態模式、備忘錄模式,(中間類) 訪問者模式、中介者模式、解釋器模式。

一.概述

定義

  觀察者模式(Observer Pattern):定義對象之間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知並被自動更新。觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式是一種對象行爲型模式。
  Observer Pattern:Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

結構

觀察者模式結構中通常包括觀察目標和觀察者兩個繼承層次結構,其結構如圖3所示:
這裏寫圖片描述
在觀察者模式結構圖中包含如下幾個角色:

  • Subject(目標):目標又稱爲主題,它是指被觀察的對象。在目標中定義了一個觀察者集合,一個觀察目標可以接受任意數量的觀察者來觀察,它提供一系列方法來增加和刪除觀察者對象,同時它定義了通知方法notify()。目標類可以是接口,也可以是抽象類或具體類。
  • ConcreteSubject(具體目標):具體目標是目標類的子類,通常它包含有經常發生改變的數據,當它的狀態發生改變時,向它的各個觀察者發出通知;同時它還實現了在目標類中定義的抽象業務邏輯方法(如果有的話)。如果無須擴展目標類,則具體目標類可以省略。
  • Observer(觀察者):觀察者將對觀察目標的改變做出反應,觀察者一般定義爲接口,該接口聲明瞭更新數據的方法update(),因此又稱爲抽象觀察者。
  • ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態,這些狀態需要和具體目標的狀態保持一致;它實現了在抽象觀察者Observer中定義的update()方法。通常在實現時,可以調用具體目標類的attach()方法將自己添加到目標類的集合中或通過detach()方法將自己從目標類的集合中刪除。
      觀察者模式描述瞭如何建立對象與對象之間的依賴關係,以及如何構造滿足這種需求的系統。觀察者模式包含觀察目標和觀察者兩類對象,一個目標可以有任意數目的與之相依賴的觀察者,一旦觀察目標的狀態發生改變,所有的觀察者都將得到通知。作爲對這個通知的響應,每個觀察者都將監視觀察目標的狀態以使其狀態與目標狀態同步,這種交互也稱爲發佈-訂閱(Publish-Subscribe)。觀察目標是通知的發佈者,它發出通知時並不需要知道誰是它的觀察者,可以有任意數目的觀察者訂閱它並接收通知。

實現

下面通過示意代碼來對該模式進行進一步分析。首先我們定義一個抽象目標Subject,典型代碼如下所示:

public abstract class Subject {    
    //定義一個觀察者集合用於存儲所有觀察者對象    
protected ArrayList observers<Observer> = new ArrayList();    

//註冊方法,用於向觀察者集合中增加一個觀察者    
    public void attach(Observer observer) {    
    observers.add(observer);    
}    

    //註銷方法,用於在觀察者集合中刪除一個觀察者    
    public void detach(Observer observer) {    
    observers.remove(observer);    
}    

    //聲明抽象通知方法    
    public abstract void notify();    
}    

具體目標類ConcreteSubject是實現了抽象目標類Subject的一個具體子類,其典型代碼如下所示:

public class ConcreteSubject extends Subject {    
    //實現通知方法    
    public void notify() {    
        //遍歷觀察者集合,調用每一個觀察者的響應方法    
        for(Object obs:observers) {    
            ((Observer)obs).update();    
        }    
    }       
}    

抽象觀察者角色一般定義爲一個接口,通常只聲明一個update()方法,爲不同觀察者的更新(響應)行爲定義相同的接口,這個方法在其子類中實現,不同的觀察者具有不同的響應方法。抽象觀察者Observer典型代碼如下所示:

public interface Observer {    
    //聲明響應方法    
    public void update();    
}    

在具體觀察者ConcreteObserver中實現了update()方法,其典型代碼如下所示:

public class ConcreteObserver implements Observer {    
    //實現響應方法    
    public void update() {    
        //具體響應代碼    
    }    
}  

在有些更加複雜的情況下,具體觀察者類ConcreteObserver的update()方法在執行時需要使用到具體目標類ConcreteSubject中的狀態(屬性),因此在ConcreteObserver與ConcreteSubject之間有時候還存在關聯或依賴關係,在ConcreteObserver中定義一個ConcreteSubject實例,通過該實例獲取存儲在ConcreteSubject中的狀態。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的狀態屬性,則可以對觀察者模式的標準結構進行簡化,在具體觀察者ConcreteObserver和具體目標ConcreteSubject之間無須維持對象引用。如果在具體層具有關聯關係,系統的擴展性將受到一定的影響,增加新的具體目標類有時候需要修改原有觀察者的代碼,在一定程度上違反了“開閉原則”,但是如果原有觀察者類無須關聯新增的具體目標,則系統擴展性不受影響。

二.源碼分析

JavaSE中已經提供了Observer接口和Observable類讓你簡單快速的實現觀察者模式,因此有必要去了解Observer和Observable;

Observer接口

Observer爲java.util包下的一個接口,源碼如下:

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

update方法第一個參數爲被觀察者對象,可以調用它的public方法。爲觀察者提供了一種拉取數據的方式
第二個參數由調用者調用notifyObservers(Object obj)將一些信息推過來。

Observable類

Observable類同樣位於java.util包,該類的成員變量和方法(省略其具體實現)如下:

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    public Observable(){};
    protected synchronized void setChanged(){};
    protected synchronized void clearChanged(){};
    public synchronized void addObserver(Observer o){};
    public synchronized void deleteObserver(Observer o) {};
    public synchronized void deleteObservers(){};
    public synchronized boolean hasChanged(){};
    public synchronized int countObservers(){};
    public void notifyObservers(){};
    public void notifyObservers(Object arg){};
}

Vector相比於ArrayList來說,它是線程安全的。其次,在添加和刪除觀察者時對兩個方法使用了synchronized關鍵字,這都是在爲多線程考慮。
最關鍵的方法是notifyObservers(),來看下源碼:

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

這個方法相當的嚴謹,考慮了線程安全等問題。
從這個方法可以看出,只有changed爲true,纔會執行各個觀察者的update方法。對changed狀態的改變的唯一途徑是protected synchronized void setChanged(){}; 而protected修飾了setChanged方法,表明只有它的子類才能調用它(打開這個開關),所以客戶端無論怎麼調用notifyObservers()方法還是無濟於事。
Observable類只提供這個boolean值來表明是否發生變化,而不定義什麼叫變化,因爲每個業務中對變化的具體定義不一樣,因此子類自己來判斷是否變化;

三.小丑表演

觀察者模式是很常用的模式,尤其在界面編程,比如android中的BaseAdapter,就使用了觀察者模式,當數據源發生變化時,通知界面重新繪製。下面我們來利用jdk中提供的Observer和Observable來實現一個觀察者模式的例子,例子非常得有趣。
需求:
鎮上來了一位小丑,爲大家表演節目,所有觀看的觀衆會根據小丑表演的精彩與否來做出相應的反應,比如表演的好就鼓掌喝彩,表演的不好就倒喝彩,表演完畢觀衆就退場。

分析:
這裏涉及到小丑和觀衆,小丑的數量是1,而觀衆的數量是n,觀衆會對小丑的舉動做出相應的反應。這裏很符合觀察者模式的場景,因此,我們需要創建一個小丑類Clown,作爲被觀察對象,因此需要繼承Observable類,同時具有表演的行爲,退場的行爲;同時需要創建一個觀衆類Viewer,作爲觀察者,因此需要實現Observer接口,同時觀衆具有喝彩的行爲,倒喝彩的行爲,退場的行爲,每個觀衆還對應一個座位號。

public class Clown extends Observable{
    public static final int PERFORM_GOOD = 0;
    public static final int PERFORM_BAD = 1;
    public static final int PERFORM_COMPLETE = 2;
    public void perfom() {
        if(countObservers()==0) {
            System.out.println("都沒人,表演個毛線!!!!");
            return;
        }
        //此參數可以由客戶端傳遞過來
        int random = new Random().nextInt(2);
        setChanged();
        notifyObservers(random);
    }
    public void exit() {
        System.out.println("表演結束,謝謝大家!");
        setChanged();
        notifyObservers(PERFORM_COMPLETE);
    }
}

public class Viewer implements Observer{
    private int seatNo;//座位號
    public Viewer(int seatNo) {
        this.seatNo = seatNo;
    }
    @Override
    public void update(Observable o, Object arg) {
        int r = (int)arg;
        switch (r) {
        case Clown.PERFORM_BAD:
            System.out.println(seatNo+"號觀衆:表演得太爛了吧!");
            if(new Random().nextBoolean()) {
                o.deleteObserver(this);
                System.out.println(seatNo+"號觀衆已經中途退場!!!!");
            }
            break;
        case Clown.PERFORM_GOOD:
            System.out.println(seatNo+"號觀衆:太好笑了,不錯不錯!");
            break;
        case Clown.PERFORM_COMPLETE:
            o.deleteObserver(this);
            System.out.println(seatNo+"號觀衆離場");
            break;
        }
    }

}
public class ObserverApp {

    public static void main(String[] args) {
        Clown c = new Clown();
        for(int i=0;i<10;i++) {
            Viewer v = new Viewer(i+1);
            c.addObserver(v);
        }
        System.out.println("觀衆都已經入座,小丑,請開始你的表演!");
        c.perfom();
        System.out.println("請在換個節目表演一次!");
        c.perfom();
        System.out.println("請在換個節目表演一次!");
        c.perfom();
        c.exit();
        c.perfom();
    }
}
觀衆都已經入座,小丑,請開始你的表演!
10號觀衆:太好笑了,不錯不錯!
9號觀衆:太好笑了,不錯不錯!
8號觀衆:太好笑了,不錯不錯!
7號觀衆:太好笑了,不錯不錯!
6號觀衆:太好笑了,不錯不錯!
5號觀衆:太好笑了,不錯不錯!
4號觀衆:太好笑了,不錯不錯!
3號觀衆:太好笑了,不錯不錯!
2號觀衆:太好笑了,不錯不錯!
1號觀衆:太好笑了,不錯不錯!
請在換個節目表演一次!
10號觀衆:表演得太爛了吧!
10號觀衆已經中途退場!!!!
9號觀衆:表演得太爛了吧!
9號觀衆已經中途退場!!!!
8號觀衆:表演得太爛了吧!
8號觀衆已經中途退場!!!!
7號觀衆:表演得太爛了吧!
6號觀衆:表演得太爛了吧!
5號觀衆:表演得太爛了吧!
5號觀衆已經中途退場!!!!
4號觀衆:表演得太爛了吧!
3號觀衆:表演得太爛了吧!
2號觀衆:表演得太爛了吧!
1號觀衆:表演得太爛了吧!
1號觀衆已經中途退場!!!!
請在換個節目表演一次!
7號觀衆:表演得太爛了吧!
6號觀衆:表演得太爛了吧!
6號觀衆已經中途退場!!!!
4號觀衆:表演得太爛了吧!
4號觀衆已經中途退場!!!!
3號觀衆:表演得太爛了吧!
2號觀衆:表演得太爛了吧!
表演結束,謝謝大家!
7號觀衆離場
3號觀衆離場
2號觀衆離場
都沒人,表演個毛線!!!!

可以參考博客:【java】Observer和Observable詳解
參考電子書下載:設計模式的藝術–軟件開發人員內功修煉之道_劉偉(2013年).pdf

《道德經》第六章:
穀神不死,是謂玄牝。玄牝(pin)之門,是謂天地之根。綿綿呵!其若存!用之不堇(jin)。
譯文:生養天地萬物的道(穀神)是永恆長存的,這叫做玄妙的母性。玄妙母體的生育之產門,這就是天地的根本。連綿不絕啊!它就是這樣不斷的永存,作用是無窮無盡的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章