菜鳥成長系列-觀察者模式

glmapper發表於2018-04-22

最近想深入研究下響應式程式設計,作為基礎很有必要來把觀察者模式擼一遍;一開始我是覺得很easy,然後就直接開擼了,擼著擼著發現擼不動了。因為我突然不太明白這個模式了,說好的觀察者,到底釋出-訂閱的兩者執行者誰才是觀察者?又或者說還有其他角色?但是根據《JAVA與模式》一書中的結構,並沒有額外的角色出現。

思考中....,好吧想不出來....,跑步去...

跑步時我給自己羅列了幾個問題:

這裡先丟擲定義:GOF給觀察者模式如下定義:定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被自動更新。

  • 既然是物件狀態發生變更,那麼到底是誰的狀態發生了變更,又導致了誰被通知。
  • 觀察者模式既然又可以稱之為“釋出-訂閱模式”,那麼對應起來,觀察者到底承當了“釋出”的角色還是“訂閱”的角色。就是說觀察者到底是主動的還是被動的?
  • 被觀察者又幹了什麼事?它是主動的還是被動的角色?

這裡由於一些定式思維,總會覺得既然是“被觀察者”,那麼這個“被”字就是不是就表明“被觀察者”是被動接受變更的一方,也就是接受通知的一方呢?

之前我也是走到這個衚衕裡了,程式寫完總覺得哪裡不對;回過頭看,還是自己太年輕,沒有get到哪些大佬們的點。

先來看程式;這裡用掘金來打個比方,我的部落格glmmaper作為被觀察者,也就是釋出者。掘金小夥伴們作為觀察者,也就是訂閱者。

具體邏輯:小夥伴們(訂閱者)關注(訂閱)了我的部落格(釋出者),如果我釋出了一篇文章(狀態變更),就會通知(推送訊息)所有關注我的小夥伴。

package com.glmapper.designmode.observor;
/**
 * @description: 抽象主題介面
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/22
 */
public interface Subject {
    /**
     * 新增關注者
     * @param observer 關注的小夥伴
     */
    void addFocusObserver(Observer observer);

    /**
     * 取消關注
     * @param observer 取消關注的小夥伴
     */
    void removeFocusObserver(Observer observer);

    /**
     * 通知機制,通知機制由相關事件來觸發,比如說釋出文章
     * @param blogName          部落格名
     * @param articleName       文章名
     */
    void notifyObservers(String blogName,String articleName);
}
複製程式碼

三個方法,一個是部落格主頁增加了一個關注者;一個是部落格主頁有小夥伴取消的關注(對於部落格來說就是移除一個關注者,這裡不知道是否也會覺得彆扭?明明你取消的關注,為啥說成是我移除你,也就是不讓你關注了,還能這麼玩?這裡肯定是需要在引入其他的一些輔助機制,比如說你在客戶端發起了一個取消關注的請求,後端處理的時候掘金的工程師們就是在我的關注列表中將你移除的,嗯,這麼一想確實是我不讓你關注了。?....);最後一個方法是發起一個通知。下面是一個具體的部落格,比如說是glmapper;

package com.glmapper.designmode.observor;

import java.util.ArrayList;
import java.util.List;

/**
 * @description: 這個是具體釋出者,這裡比喻成我的部落格glmapper
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/22
 */
public class ConcreteSubject implements  Subject {
    /** 我的當前關注列表 */
    List<Observer> Observers = new ArrayList<>();
    /** 我的部落格名 :求關注 */
    private static final String blogName = "glmapper";

    @Override
    public void addFocusObserver(Observer observer) {
        Observers.add(observer);
    }

    @Override
    public void removeFocusObserver(Observer observer) {
        Observers.remove(observer);
    }

    @Override
    public void notifyObservers(String blogName,String articleName) {
        for (Observer observer:Observers) {
            observer.update(blogName,articleName);
        }
    }
    
    /**
     * 這裡是釋出文章,觸發通知事件
     */
    public void publishArticle(String articleName){
        notifyObservers(blogName,articleName);
    }
}

複製程式碼

前面提到,通知事件肯定是由於某些狀態發生變更了,才會進行通知,這裡就可以比方為我釋出了一篇部落格,然後通知你(這裡只能假如你關注了)。再來看觀察者:

package com.glmapper.designmode.observor;

/**
 * @description: 訂閱者抽象介面
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/22
 */
public interface Observer {
    /**
     * 呼叫此方法會更新狀態,做出相應的動作
     * @param blogName
     * @param articleName
     */
    void update(String blogName,String articleName);
}
複製程式碼

抽象訂閱者,有一個update方法,通知你去做出相應的動作,具體動作每個觀察者都可能不同。

package com.glmapper.designmode.observor;

/**
 * @description: 這個是具體訂閱者,這裡可以比喻成部落格關注者,
 * 收到變更資訊之後需要做出相應的動作
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/22
 */
public class ConcreteObserver implements Observer {

    @Override
    public void update(String blogName,String articleName) {
        System.out.println(blogName+"釋出了新的文章,文章名為:"+articleName);
        read(articleName);
    }

    private void read(String articleName){
        System.out.println("即將閱讀 "+articleName+" 這篇文章");
    }
}
複製程式碼

上面是一個具體的關注者,加入說就是你。部落格更新之後發了一個通知給你(掘金app推送的訊息),然後你點了一下,這個也是一種動作。例子中舉的是read,就是關注者做出閱讀的動作。

看下最後的執行結果:

package com.glmapper.designmode.observor;

/**
 * @description: [描述文字]
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/22
 */
public class MyMainIndex{

    public static void main(String[] args) {
        //部落格主體
        ConcreteSubject subject = new ConcreteSubject();
        //關注者:handSome是帥氣的意思
        Observer handSome = new ConcreteObserver();
        //增加一個關注者
        subject.addFocusObserver(handSome);
        //發一篇文章
        subject.publishArticle("設計模式-觀察者模式");
    }

}


glmapper釋出了新的文章,文章名為:設計模式-觀察者模式
即將閱讀 設計模式-觀察者模式 這篇文章
複製程式碼

酒桶說:啊,歡樂時光總是短暫的

所以作為積累,還是需要將一些基本的概念來羅列一下的。

觀察者模式類圖

主要角色:

  • 抽象主題角色(Subject:主題角色將所有對觀察者物件的引用儲存在一個集合中,每個主題可以有任意多個觀察者。抽象主題提供了增加和刪除等觀察者物件的介面。
  • 抽象觀察者角色(Observer):為所有的具體觀察者定義一個介面,在觀察的主題發生改變時更新自己。
  • 具體主題角色(ConcreteSubject)(1個):儲存相關狀態到具體觀察者物件,當具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色通常用一個具體子類實現。
  • 具體觀察者角色(ConcretedObserver)(多個):儲存一個具體主題物件,儲存相關狀態,實現抽象觀察者角色所要求的更新介面,以使得其自身狀態和主題的狀態保持一致。

具體關係:

  • 抽象主題(Subject)(介面)-->被具體主題(ConcreteSubject)角色(1個)實現

  • 抽象觀察者(Observer)(介面)-->被具體觀察者(ConcretedObserver)角色(N個)實現

  • 觀察者物件載入主題方法,並在主題方法中呼叫觀察者物件實現的介面方法update來讓自己發生變更響應。

一些場景:

  • 當對一個物件的的改動會引發其他物件的變動時,而且你無法預測有多少個物件需要被改動。
  • 當一個物件需要有能力通知其他物件,且不需要了解這些物件是什麼型別時。

基於釋出訂閱的具體實現例子還是很多的,比較典型的就是這種訂閱一個部落格,然後部落格更新推送;還有微信公眾號,服務號這些。

到這裡我們再回過頭來看一開始留下的幾個問題:

  • 被觀察者的狀態發生變更,然後“主動通知”觀察者,並不是說,觀察者主動去獲取通知。
  • 被觀察者是訊息釋出者,觀察者是訊息訂閱者;觀察者是被動接受者。
  • 被觀察者的作用就是儲存當前的觀察者列表,然後提供一些通知機制來告訴觀察者自己發生了狀態變更,是主動者。

OK,觀察者模式就擼到這裡,也歡迎小夥伴們提出自己珍貴的意見;有寫的不當之處煩請及時提出。

播報:菜鳥成長系列又開始更新了....

相關文章