設計模式走一遍---觀察者模式(下)

帥地發表於2018-09-06

上篇我們講解了觀察者模式的一些知識,而且自定義觀察者模式的經典程式碼,(傳送們:設計模式走一遍—-觀察者模式(上))

這篇簡單講一下JDK自帶的觀察者模式實現程式碼。

對於觀察者模式,JDK中提供了一個Observer介面(觀察者),一個Observable類(主題物件)。

注:被觀察者又被稱為主題物件,目標物件。

具體我們來看下原始碼。

1.觀察者介面

public interface Observer {
    /**
     * This method is called whenever the observed *object is changed. 
     *當被觀察者發生變化時,該方法將會被呼叫
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

該介面相當於觀察者,裡面有一個update(Observable o, Object arg)方法,Observable引數是指主題物件,該引數指明該觀察者是屬於哪一個主題物件的。

arg引數可以是任意物件,假如主題物件在傳送通知時,想要傳遞什麼資料給觀察者,那麼就可以把資料物件傳遞給arg引數。

2.主題物件類(方法有點多,我就不放英文解釋了)

//主題物件可以是介面、抽象類、具體類,我們上節說
//一般採用抽象類,不過JDK這裡使用的是具體類
public class Observable {
    //標記主題物件的狀態是否改變
    private boolean changed = false;
    //存放觀察者集合,之所以用Vector而不用ArrayList
    //主要是Vector是執行緒安全的
    private Vector<Observer> obs;

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

    //標記該物件的狀態是否傳送了改變
    protected synchronized void setChanged() {
        changed = true;
    }

    //指示該物件不會再發生改變,或者它已經通知了
    //所有觀察者
    protected synchronized void clearChanged() {
        changed = false;
    }


    //測試物件是否發生了改變。當且僅當在此物件最近
    //呼叫了setChange()方法
    public synchronized boolean hasChanged() {
        return changed;
    }

    //如果hasChanged()方法指示此物件傳送了改變,
    //則通知所有觀察者,並且呼叫clearChanged()方法
    //指示此物件不再改變
    public void notifyObservers() {
        notifyObservers(null);
    }

    //與上面沒有引數的同名方法相同,只是如果這個方
    //法的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();
    }

    //返回觀察者的數量
    public synchronized int countObservers() {
        return obs.size();
    }
}

該具體類Observable相當於主題物件,實現的主要功能就是當自己的狀態傳送改變時,通知觀察者,觀察者再根據通知,在update方法做出相應的反應。

簡單寫個Demo測試下。

public class Test {
    public static void main(String[] args){
        //建立一個主題物件
        AnimalSubject animalSubject = new AnimalSubject();
        animalSubject.addObserver(new DogObsever());
        animalSubject.addObserver(new LionObsever());
        //狀態發生改變
        animalSubject.setChanged();
        //通知觀察者
        animalSubject.notifyObservers();
    }
}

//動物主題,弄子類方便擴充主題物件功能
class AnimalSubject extends Observable{
    //不過我就不新增程式碼、方法了

    //不覆蓋下的話,上面的測試呼叫不了setChange()方法
    //為了方便測試,覆蓋重寫下
    @Override
    protected synchronized void setChanged() {
        super.setChanged();
    }
}
class DogObsever implements Observer{
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("收到通知,小狗觀察者正在做出相應處理");
    }
}

class LionObsever implements Observer{
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("收到通知,獅子觀察者正在做出相應處理");
    }
}

列印結果

收到通知,獅子觀察者正在做出相應處理
收到通知,小狗觀察者正在做出相應處理

從上面的程式碼中我們可以發現JDk內建的觀察者模式中的主題物件是一個具體類,而不是一個抽象類或介面,而且setChange()方法還被保護起來了(被定義為protected),這就意味著,要在別的類中呼叫該方法,那麼我們必須繼承在子類中重寫覆蓋該方法。顯然,我覺得這很不友好…..

可能這也是JDK內建的觀察者模式很少被拿來使用 的原因吧,一般都是自己來自定義觀察者模式。

希望大家能夠動手寫一下這些程式碼,可能會碰到一些你沒想到的問題。

關注公我的眾號:苦逼的碼農,獲取更多原創文章,後臺回覆禮包送你一份時下熱門的資源大禮包。同時也感謝把文章介紹給更多需要的人

相關文章