【java】Observer和Observable詳解

轉載請標明出處:http://blog.csdn.net/u012250875/article/details/77747878

1.必要性

1.1 觀察者模式是oo設計中經常用到的模式之一,大家在解決實際需求時,觀察者模式往往都會用到,而javase中已經提供了Observer接口和Observable類讓你簡單快速的實現觀察者模式,因此有必要去了解Observer和Observable;

2.觀察者模式概述

2.1 角色:被觀察對象,觀察者

2.2 關係:
1).被觀察對象:觀察者 = 1:n
2).被觀察對象狀態發生變化,會通知所有觀察者,觀察者將做出相應的反應

2.3 詳細說明:參見【設計模式】觀察者模式

3.源碼分析

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

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

該接口約定了觀察者的行爲。所有觀察者需要在被觀察對象發生變化時做出相應的反應,所做的具體反應就是實現Observer接口的update方法,實現update方法時你可以用到兩個參數,一個參數的類型是Observable,另一個參數的類型是Object。當然如果完全由自己去實現一個觀察者模式的方案,自己去設計Observer接口時,可能不會設計這兩個參數。那爲什麼jdk設計該接口時規定接口中有這兩個參數呢?那就是通用性。想想整個觀察者模式有哪些類之間需要交互?使用該模式時牽扯三個類,一個是觀察者,一個是被觀察對象,一個是調用者(調用者可以是被觀察對象本身調用,更多情況是一個具體的業務類),當前接口代表觀察者,要與被觀察對象交互,因此update方法需要持有被觀察對象(Observable)的引用,第一參數產生了;如何與調用者通信,則是添加了類型爲Object的參數(該參數是調用者調用Observable實例的notifyObservers(Object obj)方法時傳入的,當然也可以不傳);第一個參數可以說是爲觀察者提供了一種拉取數據的方式,update中的業務可以根據所需去拉去自己想要的被觀察對象的信息(一般被觀察對象中提供getter),第二個參數則是由調用者調用notifyObservers(Object obj)將一些信息推過來。通過這兩個參數,觀察者,被觀察對象,調用者(調用通知刷新方法的可能是被觀察對象本身,此時只存在觀察者與被觀察者兩者)三者就聯繫起來了。

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

先說成員變量:
1)該類中含有一個boolean型的變量changed,代表是否發生改變了,Observable類只提供這個boolean值來表明是否發生變化,而不定義什麼叫變化,因爲每個業務中對變化的具體定義不一樣,因此子類自己來判斷是否變化;該變量既提供了一種抽象(變與不變),同時提供了一種觀察者更新狀態的可延遲加載,通過後面的notifyObservers方法分析可知觀察者是否會調用update方法,依賴於changed變量,因此即使被觀察者在邏輯上發生改變了,只要不調用setChanged,update是不會被調用的。如果我們在某些業務場景不需要頻繁觸發update,則可以適時調用setChanged方法來延遲刷新。

2)該類含有一個集合類Vector,該類泛型爲Observer,主要用來存放所有觀察自己的對象的引用,以便在更新時可以挨個遍歷集合中的觀察者,逐個調用update方法
說明:
1.8的jdk源碼爲Vector,有版本的源碼是ArrayList的集合實現;
Vector這個類和ArrayList的繼承體系是一致,主要有兩點不同,一是Vector是線程安全的,ArrayList不是線程安全的,Vector的操作依靠在方法上加了同步關鍵字來保證線程安全,與此同時ArrayList的性能是要好於Vector的;二是Vector和ArrayList擴容閥值不太一樣,ArrayList較Vector更節省空間;

再來說說方法:
1)操作changed變量的方法爲setChanged(),clearChanged(),hasChanged();見名知意,第一個設置變化狀態,第二清除變化狀態,這兩個的訪問權限都是protected,表明這兩個方法由子類去調用,由子類來告訴什麼時候被觀察者發生變化了,什麼時候變化消失,而hasChanged()方法的訪問權限是公有的,調用者可以使用該方法。三個方法都有同步關鍵字保證變量的讀寫操作線程安全。

2)操作Vector類型變量obs的方法爲addObserver(Observer o), deleteObserver(Observer o), deleteObservers(),countObservers(),這四個方法分別實現了動態添加觀察者,刪除觀察者,刪除所有觀察者,獲取觀察者數量。四個方法的訪問權限都是公有的,這是提供給調用者的方法,讓調用者來實時動態的控制哪些觀察者來觀察該被觀察對象。

3)操作Vector型變量obs的四個方法都加有同步關鍵字,但是我們剛纔分析成員屬性Vector obs這個變量時,說Vector類型爲線程安全的,而上述四個方法爲什麼還要加同步關鍵字呢,這是怎麼回事?據我推測應該是程序員重構遺留問題吧,因爲前面我說道,有歷史版本的源碼是使用的ArrayList來持有Observer的引用,而ArrayList不是線程安全的,所以上述四個操作結合的方法需要加上同步關鍵字來保證線程安全,而後來換成線程安全的Vector了,但這四個操作集合的方法依舊保留了同步關鍵字。

4)兩個對外的方法notifyObservers(),notifyObservers(Object arg),該方法由調用者來操作,用來通知所有的觀察者需要做更新操作了。

先不看源碼,想想應該怎麼做呢?通知觀察者們進行刷新操作,不就是用for循環一個一個操作集合中的Observer調用update方法嘛,這還不簡單,於是版本一產生:

//版本一
public void notifyObservers(Object arg) {
    if(changed){
        for (int i = 0; i<obs.size(); i++)
            obs.get(i).update(this, arg);
    }
}

看看版本一,很容易發現,該方法會出很多問題,首先調用了所有的觀察者的update方法,但是沒有清除被觀察的變化狀態,由於changed變量狀態沒有重置,因此,如果notifyObservers被多次調用,即使Observable沒有再發生變化,所有觀察者的update方法已經會被執行。因此需要進行修改,如下:

//版本二
public void notifyObservers(Object arg) {
    clearChanged();
    if(changed){
        for (int i = 0; i<obs.size(); i++)
            obs.get(i).update(this, arg);
    }
}

看看版本二,依舊有問題,如果出現併發時,各線程對changed變量的讀寫操作不安全,可能出現髒讀因此產生重複update或者不能update的情況,因此需要進行修改,如下:

//版本三
public synchronized void notifyObservers(Object arg) {
    clearChanged();
    for (int i = 0; i<obs.size(); i++)
        obs.get(i).update(this, arg);
}

看看版本三,不會出現併發造成的變量狀態不一致帶來的錯誤操作,但是想一想觀察者數量較多時或者update方法執行時間較長時,被觀察者變化後,notifyObservers的執行時間大大增加,呈線性增長,比如併發數是20,而此時有10個線程發生changed並且調用了notifyObservers方法,那麼10個線程執行該方法將進入同步,粗略計算耗時爲10*for循環執行時長,因此需要進行修改,我們只對changed變量的讀寫部分加鎖,不會引起變量狀態的不一致性,同時當同步塊的代碼執行完畢後,該線程可以先去執行耗時的for循環,修改如下:

//版本四
public void notifyObservers(Object arg) {
    synchronized (this) {
        if (!changed)
           return;
        clearChanged();
    }
    for (int i = 0; i<obs.size(); i++)
        obs.get(i).update(this, arg);
}

看看版本四,發現還是存在問題雖然解決了耗,但依舊會有問題,多線程在同步塊進行了同步,但是執行for循環的時候,由於調用者可能不斷在增刪觀察者,假如A線程剛執行完i

//源碼的方案
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);
    }

源碼果然很嚴謹!!!

5)上面說了系統對觀察者模式的支持有這麼多優點,但依舊不可避免以下幾個缺點:
A. Observable是一個具體實現類,面向細節了,而未面向抽象
B. 使用Observable時需要使用繼承,由於java的類單繼承性,如果你的類已經繼承了一個類,將不能繼承Observable來實現觀察者模式,並且由於setChanged和clearChanged方法都是protected的,所以你也不能通過組合來完成觀察者模式

4.應用

觀察者模式是很常用的模式,尤其在界面編程,比如android中的BaseAdapter,就使用了觀察者模式,當數據源發生變化時,通知界面重新繪製。下面我們來利用jdk中提供的Observer和Observable來實現一個觀察者模式的例子。

需求:
鎮上來了一位小丑,爲大家表演節目,所有觀看的觀衆會根據小丑表演的精彩與否來做出相應的反應,比如表演的好就鼓掌喝彩,表演的不好就倒喝彩,表演完畢觀衆就退場。

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

實現:

import java.util.Observable;
import java.util.Random;

/**
 * @author puyafeng 
 * @desc 小丑類
 */
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 perform() {
        System.out.println("**小丑開始表演**");

        int random = new Random().nextInt(2);
        //小丑表演狀態是隨機值,0表演的好,1表演的差
        switch (random) {
            case PERFORM_GOOD:
                System.out.println("**小丑狀態很好,表演的很精彩!**");
                break;
            case PERFORM_BAD:
                System.out.println("**小丑狀態不好,出了點簍子!**");
                break;
        }
        setChanged();
        notifyObservers(random);//表演好壞通過該參數傳遞到觀衆的update方法的第二個參數上
    }

    /**
     * 表演結束,小丑退場
     */
    public void exit() {
        System.out.println("**表演結束,小丑退場!**");
        setChanged();
        notifyObservers(PERFORM_COMPLETE);//退場消息通過該參數傳遞到觀衆的update方法的第二個參數上
    }

}
import java.util.Observable;
import java.util.Observer;

/**
 * @author puyf
 * @desc 觀衆類
 */
public class Viewer implements Observer {
    private int seatNo;

    public Viewer(int seatNo) {
        this.seatNo = seatNo;
    }

    @Override
    public void update(Observable o, Object arg) {
        Integer state = (Integer) arg;
        switch (state) {
            case Clown.PERFORM_GOOD:
                applause();
                break;
            case Clown.PERFORM_BAD:
                CheerBack();
                break;
            case Clown.PERFORM_COMPLETE:
                exit();
                break;
            default:
                break;
        }
    }

    /**
     * 鼓掌
     */
    private void applause() {
        System.out.println("座位號" + getSeatNo() + "的觀衆鼓掌了!");
    }

    /**
     * 倒喝彩
     */
    private void CheerBack() {
        System.out.println("座位號" + getSeatNo() + "的觀衆發出了倒喝彩!");
    }

    /**
     * 退場
     */
    private void exit() {
        System.out.println("座位號" + getSeatNo() + "的觀衆退場!");
    }

    public int getSeatNo() {
        return seatNo;
    }

}
/**
 * 
 * @author puyf
 * @desc 測試類
 */
public class Test {
    public static void main(String[] args) {
        //來了一個小丑
        Clown clown = new Clown();
        //觀衆入場了
        for (int i = 0; i < 20; i++) {
            Viewer v = new Viewer(i);
            clown.addObserver(v);
            System.out.println("座號爲"+v.getSeatNo()+"的觀衆入座");
        }
        //小丑開始表演
        clown.perform();
        //小丑表演完畢,退場
        clown.exit();
    }
}

執行結果:

座號爲0的觀衆入座
座號爲1的觀衆入座
座號爲2的觀衆入座
座號爲3的觀衆入座
座號爲4的觀衆入座
座號爲5的觀衆入座
座號爲6的觀衆入座
座號爲7的觀衆入座
座號爲8的觀衆入座
座號爲9的觀衆入座
座號爲10的觀衆入座
座號爲11的觀衆入座
座號爲12的觀衆入座
座號爲13的觀衆入座
座號爲14的觀衆入座
座號爲15的觀衆入座
座號爲16的觀衆入座
座號爲17的觀衆入座
座號爲18的觀衆入座
座號爲19的觀衆入座
**小丑開始表演**
**小丑狀態不好,出了點簍子!**
座位號19的觀衆發出了倒喝彩!
座位號18的觀衆發出了倒喝彩!
座位號17的觀衆發出了倒喝彩!
座位號16的觀衆發出了倒喝彩!
座位號15的觀衆發出了倒喝彩!
座位號14的觀衆發出了倒喝彩!
座位號13的觀衆發出了倒喝彩!
座位號12的觀衆發出了倒喝彩!
座位號11的觀衆發出了倒喝彩!
座位號10的觀衆發出了倒喝彩!
座位號9的觀衆發出了倒喝彩!
座位號8的觀衆發出了倒喝彩!
座位號7的觀衆發出了倒喝彩!
座位號6的觀衆發出了倒喝彩!
座位號5的觀衆發出了倒喝彩!
座位號4的觀衆發出了倒喝彩!
座位號3的觀衆發出了倒喝彩!
座位號2的觀衆發出了倒喝彩!
座位號1的觀衆發出了倒喝彩!
座位號0的觀衆發出了倒喝彩!
**表演結束,謝謝各位觀看,請各位觀衆退場!**
座位號19的觀衆退場!
座位號18的觀衆退場!
座位號17的觀衆退場!
座位號16的觀衆退場!
座位號15的觀衆退場!
座位號14的觀衆退場!
座位號13的觀衆退場!
座位號12的觀衆退場!
座位號11的觀衆退場!
座位號10的觀衆退場!
座位號9的觀衆退場!
座位號8的觀衆退場!
座位號7的觀衆退場!
座位號6的觀衆退場!
座位號5的觀衆退場!
座位號4的觀衆退場!
座位號3的觀衆退場!
座位號2的觀衆退場!
座位號1的觀衆退場!
座位號0的觀衆退場!

當然上面的Clown類中的perform方法和exit方法中調用了notifyObservers(Object obj)方法,有時候是在業務邏輯中調用該方法來通知,比如去掉perform和exit中的notifyObservers,而在我們這裏的Test類中的main()方法 來根據具體的業務邏輯來調用notifyObservers並傳入參數。

發佈了28 篇原創文章 · 獲贊 63 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章