設計模式——觀察者衆

前 言

  寫設計模式的技術大佬不少,布衣博主深感自身技術粗淺,原本不想人從衆,但對於知識的理解、應用每一個技術人仍是有很大的不一樣的——若是把某種技術抽象來看的話,那麼這種技術被不一樣的技術人員實現出來就會產生千人千種哈姆雷特的紛繁效果。所謂 紙上得來終覺淺,絕知此事要躬行。別人理解的終究是別人的,你看懂了不必定會運用,本身如何內化吸取,學以至用纔是關鍵。所以,基於對技(zhuang)術( bi )的熱( ke )愛(wang),博主有時間,仍是會講點設計模式方面的知識。不過設計模式畢竟是思想層面的東西,怎麼運用纔是關鍵,因此博主會重點着筆於設計模式的現實運用 ,而不僅是乾巴巴的概念化講述。寫的不必定好,但保證必定很用心。java

模式理解

  觀察者模式,若是要好理解的話,應該稱其爲 發佈——訂閱模式。聯想一下之前報紙橫行的時代的訂閱報紙以及現在微信時代的訂閱公衆號等現實場景,從對象關係模型的角度來分析,這兩種場景都會涉及兩大主體對象消息的訂閱者和消息的發佈者。產生的實際效果就是,你訂閱了報紙,那麼報紙發行的時候天然少不了你的一份;你訂閱了某公衆號,那麼公衆號有新動態的時候也會實時的推送到你面前。能夠看出,發佈者訂閱者之間的關係,是一種很明顯的一對多關係。不過,在正式的設計模式稱謂中,咱們稱消息發佈者爲主題Subject【sʌbdʒɪkt】),稱消息訂閱者爲觀察者Observer 【əb'zɜːvə】),故博主才曰觀察者衆。至此,從理解層面上來講,若是你不是傻子的話,對於觀察者模式,你確定已經知其然了。編程

模型分析

  就像不少道理知易行難同樣,雖然觀察者模式比較好理解,可是要怎麼將其設計成可以開發運用的對象關係模型,仍是要費一番思量。這裏,博主先不直接上UML類圖,度娘一下就有,先按照上面的理解,來設計草稿。從模式理解能夠分析出,消息的訂閱者至少須要兩個基本功能:訂閱取消訂閱,而消息發佈者的功能就是發佈消息,可是發佈消息應該是一種廣播的方式要將更新的消息發佈給全部的訂閱者對象,因此,其內部應該維護一個存儲全部對象引用的集合。按照這樣簡單的理解話,草圖大概就是博主以下醜畫的模樣:設計模式

            

   自我感受畫的很到位,真正的在設計類圖的時候,按照上圖去設計,是有必定問題的。從語言描述上,訂閱者根據須要訂閱或取消訂閱,彷佛訂閱和取訂是訂閱者本身的功能,但仔細分析則否則,應該是消息的發佈者【主題】暴露給消息訂閱者【觀察者】的功能項。由於消息的發佈者內部須要維護一個裝有全部訂閱者的集合,實際上就是將對集合操做(添加刪除訂閱者)的API暴露給了訂閱者,而這也是觀察者模式中 主題 對象中 註冊 和 撤銷註冊 的本質;而至於消息發佈者如何將消息發佈給消息的訂閱者呢?不用想複雜了,就是一個簡單的發佈者對訂閱者方法的調用而已,只不過,發佈者要通知全部訂閱者的話必需要遍歷集合挨個的調用訂閱者的消息接收方法。在設計模式中,咱們是要面向接口去設計編程模型的,因而將發佈者的註冊、撤銷註冊和訂閱者的消息接收等功能都抽象成了抽象方法,將發佈者和訂閱者也抽象成了主題接口觀察者接口。到此,基本上,UML類圖你就能夠畫了,下圖爲百度詞條上的UML類圖:微信

                      

  

  須要說明的是,觀察者模式只是一種設計思想,你只要搞清楚主題【Subject】觀察者【Observer】之間的關聯關係就夠了,至於具體的抽象接口如何去設計,是要根據本身的業務需求進行靈活變更的,還可添加本身業務相關的其它功能項,以上UML圖能夠參考,但千萬不能硬套,不然就會囿於模式,裹足難行。異步

官方生態

  對於觀察者模式的用武之地,博主反覆查詢了一下,彷佛在Java的GUI工具包Swing中才有比較多的應用,不過業界彷佛對Java的圖形化界面開發不太感冒,博主也無力講解。可是對於觀察者模式的實現,JDK中是早就有現成的一套API——分別是 java.util.Observer接口【觀察者】和 java.util.Observable 類【主題】,有興趣的能夠簡單瞭解:ide

public interface Observer {
    //主題對象發生改變,該方法被調用
    void update(Observable o, Object arg);
}
public class Observable {
    //標記對象狀態,狀態爲true表示對象改變,纔會通知觀察對象
    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();
    }

    /**
     *  標記對象狀態爲 true 已改變
     *  注意這是 protected 方法,也就是說,你只有繼承該類才能執行狀態改變,不然沒法應用該類提供的通知模式 
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 標記對象再也不改變
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

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

  這裏只簡單的帶你們分析了下源碼,若有可能,你或能夠學得一些設計技巧,除此以外,就不必深究了,由於JDK本身的這套觀察者模式的 API 並不推薦你們使用,彷佛也沒見人用過。一方面,其做爲JDK1.0 就出現的元老級API是有些過期,其存放觀察者的容器是一樣不推薦使用的Vector,另外一方面也是最重要的一點就是其主題對象被設計成了一個類而不是接口,基於Java單繼承的原則,你的類認了該類爲父,那擴展性就會大打折扣。探訪源碼博主還發現一個細節,就是Observer 和 Observable 一出生就很孤獨,居然在JDK內部都沒有二者的任何擴展(繼承或實現)或引用,可見其雖爲親生,可是多麼的不受待見。固然,API雖然不推薦你們使用,可是僅就源碼的寫法或者技巧來講,仍是有值得學習的方面,怎麼說呢,去其糟粕,取其精華吧!工具

場景運用 

  設計模式只是將待解的問題抽象化而已,真正的運用難點是還原到實際場景自己,要結合實際場景進行靈活變化,但思想不離其宗。學習

  這裏博主舉一個線程回調的例子。咱們知道,Java中線程啓動後的執行的 run 方法是沒有返回值的,爲了獲取線程任務執行的結果,一般的作法就是回調(PS:固然能夠用 Future 的方式來獲取異步執行結果,這也是一種方式),在 run 方法執行完畢的時候執行回調方法將子線程的執行結果返回。若是有多個地方須要子線程的執行結果,就能夠採用觀察者模式來進行操做。先定義主題和觀察者接口:測試

//主題抽象
public interface Subject {
     void registerObserver(ICallback callback);
     void removeObserver(ICallback callback);
     void notifyObserver();
}


//觀察者抽象 根據業務須要本身定義觀察者動做
public interface ICallback {
    void update(String result);
}

  定義線程任務:this

public class MyThread implements Runnable, Subject {
    private List<ICallback> obs;
    private String res;
    MyThread() {
        obs = new ArrayList<>();
    }

    @Override
    public void run() {
        //TODO 執行線程任務  模擬執行任務延時 後獲取任務結果
        try {
            TimeUnit.SECONDS.sleep(1);
            res = "號外號外,又到週末了,放假啦。。。。。。嗨起來";
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 通知觀察者(們)
        notifyObserver();
    }

    @Override
    public void registerObserver(ICallback callback) {
        obs.add(callback);
    }

    @Override
    public void removeObserver(ICallback callback) {
        obs.remove(callback);
    }

    @Override
    public void notifyObserver() {
        //通知觀察者
        for (ICallback callback : obs) {
            callback.update(res);
        }
    }
}

  觀察者們:

public class Tom implements ICallback {
    @Override
    public void update(String result) {
        System.out.println("Tom received:"+result);
    }
}

。。。。

public class Rubi implements ICallback {

    @Override
    public void update(String result) {
        System.out.println("Rubi received:"+result);
    }
}

  測試:

public class Test {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.registerObserver(new Tom());  // 註冊 Tom
        mt.registerObserver(new Rubi()); // 註冊 Rubi
        new Thread(mt).start();   // 在主線程中開啓子線程任務
    }
}

----------------執行結果--------------------------------------

Tom received:號外號外,明天又放假了。。。。。。嗨起來
Rubi received:號外號外,明天又放假了。。。。。。嗨起來

 

  OVER !

相關文章
相關標籤/搜索