設計模式解析-1:觀察者模式

Brick發表於2018-07-31

1、介面回撥

在學習觀察者模式之前,我們先了解一下介面回撥的概念。兩者的原理有些類似,理解了介面回撥就非常容易理解觀察者模式。

所謂介面回撥一般應用的場合是:你不知道這個方法什麼時候回返回,但是你希望在該方法結束的時候拿到方法執行的結果。常見的,比如一個方法內部開啟了執行緒,而我們希望線上程執行結束的時候拿到執行緒的執行結果。

在下面的例子中,方法 cal() 定義了一個區域性變數 i,隨後開啟了一個執行緒,並線上程執行的時候修改了 i 的值。我們希望線上程執行完畢的時候達到 i 的執行結果。

    public int cal() {
        int i;
        new Thread(new Runnable() { 
            Thread.sleep(3000); // throw ... ignore it
            i++; 
        }).start();
        return i;
    }
複製程式碼

使用 return 肯定不行,因為方法結束時,執行緒可能還沒有結束,那麼 return 返回的結果是無法被預料到的,可能是執行緒執行完畢之後的,也可能是沒有被執行緒修改就返回了的。

我們可以使用介面回撥解決這個問題。我們可以在呼叫該方法的時候傳入一個介面 Callback 的例項。線上程中執行完所有的邏輯之後,我們使用該介面的 call() 方法將 i 回撥出來:

    public void cal(Callback callback) {
        int i;
        new Thread(new Runnable() { 
            Thread.sleep(3000); // throw ... ignore it
            i++; 
            if (callback != null) {
                callback.call(i); // 1
            }
        }).start();
    }
複製程式碼

當然,介面回撥更像是將我的 call() 方法注入到了上述程式碼中的 1 處。這近似於所謂的函式程式設計的概念,就是將介面作為一個函式注入到了方法中。

介面回撥有非常豐富的應用場景,典型的是一些非同步的場景,比如 “監聽” 一個按鈕的執行結果等等。

2、觀察者模式

好了,瞭解了上面的介面回撥之後,我們來看下觀察者模式。首先,我們來了解一下觀察者設計模式中的一些概念。

如果使用 1 中的的例子來做類比的話,那麼 i 就是我們的主題(Subject),我們進行介面回撥的類(將實現的 Callback 傳入到 cal 方法時所處的類)叫做觀察者(Subscriber)

觀察者模式UML

上面的是觀察者模式的UML模型。這裡的 Subject 介面就是主題的介面,而 ConcreteSubkect 是它的具體實現類,即具體的 主題。Observer 介面是觀察者的介面,ConcreteObserver 是具體的觀察者。一個主題往往會通過列表來維護一系列的觀察者,然後當主題發生變化的時候會便利這個列表來通知所有的觀察者。所以,這裡的 Subject 介面定義了三個方法,從上到下依次用於向主題中新增觀察者,從主題中移除觀察者以及通知所有的觀察者主題的更新。

下面我們給出一份最簡單的觀察者設計模式的程式碼:

1.首先是主題的定義類:

    public class ConcreteSubject implements Subject {

        // 通過佇列維護觀察者列表
        private List<Observer> observers = new LinkedList<>();

        // 註冊一個觀察者
        @Override
        public void registerObserver(Observer o) {
            observers.add(o);
        }

        // 移除一個觀察者
        @Override
        public void removeObserver(Observer o) {
            int i = observers.indexOf(o);
            if (i >= 0) {
                observers.remove(o);
            } 
        }

        // 通知所有觀察者主題的更新
        @Override
        public void notifyObservers() {
            for (Observer o : observers) {
                o.method();
            }
        }
    }
複製程式碼

2.接下來是觀察者的定義類:

    public class ConcreteObserver implements Observer {
        
        // 該觀察者訂閱的主題
        private Subject subject;

        public ConcreteObserver(Subject subject) {
            this.subject = subject;
            // 將當前觀察者新增到主題訂閱列表中
            subject.registerObserver(this);
        }
        
        // 當主題發生變化的時候,主題會遍歷觀察者列表並通過呼叫該方法來通知觀察者
        @Override
        public void method() {
            // ...  
        }
    }
複製程式碼

上面就是觀察者的基本的實現方式,這裡的實現邏輯比較簡單,但當你學習更加複雜的觀察者設計的之前理解它是很有必要的。

3、總結

觀察者與被觀察者之間是屬於輕度的關聯關係,並且是抽象耦合的,這樣,對於兩者來說都比較容易進行擴充套件。

觀察者模式是一種常用的觸發機制,它形成一條觸發鏈,依次對各個觀察者的方法進行處理。但同時,這也算是觀察者模式一個缺點,由於是鏈式觸發,當觀察者比較多的時候,效能問題是比較令人擔憂的。並且,在鏈式結構中,比較容易出現迴圈引用的錯誤,造成系統假死。並且因為觀察者列表中維護了一份觀察者的引用,當它們沒有被及時地釋放的話,可能會引起記憶體洩漏。

相關文章