【一起學系列】之觀察者模式:我沒有在監控你啊

意圖

定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新

別名:發佈-訂閱模式

觀察者模式的誕生

將一個系統分割成一系列相互協作的類有一個常見的副作用:需要維護相關對象間的致性,我們不希望爲了維持一致性而使各類緊密耦合,因爲這樣降低了它們的可重用性。

說人話就是:

產品】:開發小哥,我需要你設計一個天氣預報顯示大屏,氣象站會給你發送數據,你需要把它展示到大屏裏,OK嗎?

開發】:OJBK!秒秒鐘搞定一切!代碼立馬出來!

void getTemperature () {
    // 從氣象站獲取發送過來的溫度數據
    // getData();
    
    
    // ................................
    // 顯示到大屏裏面去
    // showDataToScreen();
    // ................................
}

void getMisture () {
    // 從氣象站獲取發送過來的溼度數據
    // getData();
    
    
    // ................................
    // 顯示到大屏裏面去
    // showDataToScreen();
    // ................................
}

void getAirindex () {
    // 從氣象站獲取發送過來的空氣指數數據
    // getData();
    
    
    // ................................
    // 顯示到大屏裏面去
    // showDataToScreen();
    // ................................
}

BOSS】:磕大頭!寧是準備每獲取一次數據就把代碼CV一遍嗎?你不累嗎?

開發】:老大,我一點都不累!就是複製粘貼一下呀!

BOSS】:如果我現在不需要同步更新天氣指數呢?刪代碼嗎?

開發】:對啊!一秒鐘就能刪掉!( •̀ ω •́ )✧

BOSS】:重寫😃

HeadFirst 核心代碼

於是乎,我們開啓了關於設計模式的經典書籍閱讀之旅

/**
 * 觀察主題接口
 */
public interface Observable{
    public void addObserver(Observer observer);     // 添加觀察者
    public void removeObserver(Observer observer);  // 移除觀察者
    public void notifyObservers(WeatherData data);  // 通知所有觀察者
}


/**
 * 觀察者
 */
public interface Observer {
    public abstract void update(WeatherData data);
}


/**
 * 天氣主題
 *
 */
public class Weather implements Observable {
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    
    public void notifyObservers(WeatherData data) {
        for (Observer observer : observers)
            observer.update(data);
    }
}

觀察者模式的設計思路:

  • Subject 目標(容器)提供註冊和刪除觀察者的接口以及更新接口
  • Observer(觀察者)爲那些在目標發生改變時需獲得通知的對象定義一個更新接口
  • ConcreteSubject(具體目標)狀態發生改變時,向各個觀察者發出通知
  • ConcreteObserver(具體觀察者)實現Observer的更新接口

簡單來說,

  1. 我們需要一個接口來定義註冊,刪除和更新接口
  2. 然後由具體的目標(類)實現該接口,並且在類中創建一個容器,存儲需要被通知的對象
  3. 需要被通知的對象,需要實現Observer接口中的update更新方法
  4. 將觀察者對象註冊進容器中,當具體目標更新時,調用所有容器類對象的update方法

如果看着有點模棱兩可,就看完本文後,訪問專題設計模式開源項目,裏面有具體的代碼示例,鏈接在最下面

JDK中的觀察者模式

JDK中已經對觀察者模式有具體的實現,代碼非常簡單,如下所示:

具體目標:

public class ObservableApp extends Observable {

    private long curr;
    
    public ObservableApp(long curr) {
        this.curr = curr;
    }
    
    public void change(long newStr) {
        this.curr = newStr;
        
        // 更改狀態,發送通知
        setChanged();
        notifyObservers(newStr);
    }

    @Override
    protected synchronized void setChanged() {
        super.setChanged();
    }
}

具體觀察者:

public class ObserverA implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println(MessageFormat.format("ObserverA -> {0} changed, Begin to Work. agr is:{1}", o.getClass().getSimpleName(), arg));
    }
}

Main方法:

public class App {
    
    public static void main(String[] args) throws InterruptedException {
        ObservableApp app = new ObservableApp(System.currentTimeMillis());
        System.out.println(app.getCurr());
        app.addObserver(new ObserverA());
        app.addObserver(new ObserverB());

        Thread.sleep(1000);
        
        long curr = System.currentTimeMillis();
        app.change(curr);
        System.out.println(app.getCurr());
    }
}

// 輸出如下:
// 1589688733464
// ObserverB -> ObservableApp changed, Begin to Work. agr is:1,589,688,734,469
// ObserverA -> ObservableApp changed, Begin to Work. agr is:1,589,688,734,469
// 1589688734469

推模式

通知發給觀察者,通知攜帶參數,這就是推,對應JDK方法中的:notifyObservers(Object arg)

拉模式

通知發給觀察者,通知不攜帶參數,需要觀察者自己去主動調用get方法獲取數據,這就是拉

對應JDK方法中的:notifyObservers(),僅告知觀察者數據發生了變化,至於數據的詳情需要觀察者主動到主題中pull數據

拉模型強調的是目標不知道它的觀察者,而推模型假定目標知道一些觀察者的需要的信息。推模型可能使得觀察者相對難以複用,因爲目標對觀察者的假定可能並不總是正確的。另一方面。拉模型可能效率較差,因爲觀察者對象需在沒有目標對象幫助的情況下確定什麼改變了。

遵循的設計原則

  1. 封裝變化
    • 在觀察者模式中會經常改變的是主題的狀態,以及觀察者的數目和類型
    • 我們可以改變依賴於主題狀態的對象,但是不必改變主題本身,這便是提前規劃
  2. 針對接口編程
    • 主題和觀察者都使用了接口
    • 觀察者利用主題的接口向主題註冊
    • 主題利用觀察者接口通知觀察者,可以使兩者之間正常交互,同時又具有鬆耦合的特性
  3. 多使用組合
    • 觀察者模式利用組合將許多觀察者組合進主題中
    • 它們之間的關係並不是通過繼承得到,而是在運行時動態改變

什麼場景適合使用

當對象間存在一對多關係時,則使用觀察者模式(Observer Pattern),比如,當一個對象被修改時,則會自動通知它的依賴對象。觀察者模式屬於行爲型模式

Code/生活中的實際應用

  • 比如微信公衆號中的訂閱關注,訂閱後,公衆號發佈文章會實時分發給各個賬號
  • 又如,我們使用Keep跑步時,如果你跑的足夠激情,它會提示你,恭喜你,你已經打破了五公里的最好記錄!這樣的語音提醒一定是觸發式,而不是實時去檢測吧?(實時檢測沒有意義,浪費性能)這裏就可以利用觀察者模式進行設計和解耦

最後

附上GOF一書中對於觀察者模式的UML圖:

觀察者模式UML圖

相關代碼鏈接

GitHub地址

  • 兼顧了《HeadFirst》以及《GOF》兩本經典數據中的案例
  • 提供了友好的閱讀指導

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章