觀察者模式

訂購雜誌案例

場景

小明和小劉十分喜歡一款名爲《電腦愛好者》的雜誌,就像公衆號的推送同樣,他們想獲得這款雜誌的出版消息,而後去書店購買。java

clipboard.png

笨拙的實現

當前雜誌設計模式

/**
 * 當前雜誌/最新雜誌
 */
public class CurrentMagazine {

    private String name;              // 當前雜誌名稱

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        this.inform();                // 雜誌更新時調用inform方法通知小明和小劉
    }

    private void inform() {
        XiaoMing.update(this);
        XiaoLiu.update(this);
    }
}

小明與小劉實現相關update方法,當雜誌更新時通知小明和小劉。ide

/**
 * 小明
 */
public class XiaoMing {

    public static void update(CurrentMagazine magazine) {
        System.out.println("小明:" + magazine.getName() + "出版了,我要去書店購買。");
    }
}

/**
 * 小劉
 */
public class XiaoLiu {

    public static void update(CurrentMagazine magazine) {
        System.out.println("小劉:" + magazine.getName() + "出版了,我要去書店購買。");
    }
}

main方法:優化

public class Main {

    public static void main(String[] args) {
        CurrentMagazine currentMagazine = new CurrentMagazine();
        currentMagazine.setName("電腦愛好者2018年第10期");
        currentMagazine.setName("電腦愛好者2018年第11期");
    }
}

運行結果:this

clipboard.png

爲何說這個實現不太優雅,這種實現是在inform中通知小明和小劉更新雜誌的,可是這是常常變化的,可能又來幾我的想訂購雜誌。咱們要改動的地方有兩處。spa

爲該人員實現update方法,同時在inform中通知該人員。改動的地方多了,天然可能被遺忘,咱們須要一個一勞永逸的方案。設計

優化

clipboard.png

inform中的代碼是高度重複的,都是調用update並傳this進去,而且這個訂購雜誌的人是常常變更的。因此咱們須要創建一個訂閱該雜誌的一些人的集合。code

List readers = new ArrayList();

而後咱們在inform中就不用一行一行的寫我要通知誰,直接遍歷這個列表,都去調用其中的updateorm

相信你已經發現了問題,由於這裏沒有泛型,因此從List中取出來的對象都是Object類型,咱們怎麼能保證其必定有update方法呢?怎麼能保證這些update的參數都相同呢?因此咱們須要一個聲明update的接口。server

/**
 * 接口:可更新
 */
public interface Updatable {
    void update(Magazine magazine)
}

List<Updatable> readers = new ArrayList<Updatable>();     // 訂購雜誌的人員列表

/**
 * 優化後的inform方法
 */
public void inform() {
    for (Updatable reader : readers) {
        reader.update(this);
    }
}

優化以後,咱們進行訂閱變動時,無需修改inform方法,咱們只須要去維護訂購該雜誌的人員列表就好了,減少了響應需求變動的成本。

觀察者模式

若是你能理解我上面說的是什麼,那麼恭喜你。你已經明白了觀察者模式。

都說觀察者模式是在JDK實現中使用的最多的設計模式,這裏姑且相信,畢竟,我也沒有閱讀過JDK的源碼。

JDK中已經爲咱們提供了觀察者模式的基礎,一個可供訂閱的類、一個描述交互消息的接口,這就是觀察者模式。

JDK中可供訂閱的基類:

package java.util;

public class Observable {
    private boolean changed = false;
    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);
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

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

    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}

JDK中描述交互消息的接口:

package java.util;

public interface Observer {

    void update(Observable o, Object arg);
}

實現代碼

用於被訂閱的雜誌:

import java.util.Observable;

/**
 * 當前雜誌/最新雜誌
 */
public class CurrentMagazine extends Observable {

    private String name;                  // 當前雜誌名稱

    public void setName(String name) {
        this.name = name;
        /**
         * 這裏設置改動的緣由是在notifyObservers中
         * 判斷了是否有改動,若是改動爲false,則終止
         */
        this.setChanged();                // 設置有變化
        this.notifyObservers();           // 通知觀察者
    }

    public String getName() {
        return name;
    }
}

觀察者,小明與小劉:

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

/**
 * 小明
 */
public class XiaoMing implements Observer {

    @Override
    public void update(Observable observable, Object arg) {
        if (observable instanceof CurrentMagazine) {
            CurrentMagazine currentMagazine = (CurrentMagazine) observable;
            System.out.println("小明:" + currentMagazine.getName() + "出版了,我要去書店購買。");
        }
    }
}

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

/**
 * 小劉
 */
public class XiaoLiu implements Observer {

    @Override
    public void update(Observable observable, Object arg) {
        if (observable instanceof CurrentMagazine) {
            CurrentMagazine currentMagazine = (CurrentMagazine) observable;
            System.out.println("小劉:" + currentMagazine.getName() + "出版了,我要去書店購買。");
        }
    }
}

思考一下爲何這裏須要判斷當前Observable的類型呢?

小明和小劉能夠訂購多個消息,能夠訂購雜誌,也能夠訂購天氣預報。可是二者調用的都是update方法,因此要用instanceof判斷到底是我訂閱的哪一個服務推送的消息,而後再進行相應處理。

main方法:

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

public class Main {

    public static void main(String[] args) {
        Observable currentMagazine = new CurrentMagazine();     // 聲明被訂閱的雜誌

        Observer xiaoMing = new XiaoMing();                     // 聲明觀察者
        Observer xiaoLiu = new XiaoLiu();

        currentMagazine.addObserver(xiaoMing);                  // 觀察者訂閱雜誌
        currentMagazine.addObserver(xiaoLiu);

        ((CurrentMagazine) currentMagazine).setName("電腦愛好者2018年第10期");
        ((CurrentMagazine) currentMagazine).setName("電腦愛好者2018年第11期");
    }
}

聲明雜誌,聲明觀察者,讓觀察者訂閱雜誌,而後修改雜誌信息,觀察者會受到消息通知。

運行結果:

clipboard.png

一處代碼細節

還記得代碼整潔之道中有一條規範,就是咱們的方法參數個數越少越好。參數越多,出錯的可能性就會越大。

clipboard.png

這是Head First設計模式中的一處代碼片斷。第一眼看到這段代碼,由於不符合個人風格嘛,天然多思考一下。

若是讓我去實現這個display,我會爲其設置參數。

public void display(float tempature, float humidity) {
    System.out.println("Current Conditions: " + tempature + "F degrees and " + humidity + "% humidity");
}

對比之下,仍是感受書上的實現好。在類的內部,消息溝通天然隨意一些,直接使用內部變量便可溝通。而那些方法中的參數,都是本類不能本身提供,須要從外界獲取的內容。如此,就減小了參數的個數,下降了交流的成本。

類內部,使用變量進行溝通,畢竟,封裝的本質,就是減小各個操做之間數據的交流成本。之後多注意。
相關文章
相關標籤/搜索