17.java設計模式之觀察者模式

xiaokantianse發表於2020-12-03

基本需求:

  • 氣象站可以將每天測量到的溫度,溼度,氣壓等等,以公告的形式釋出出去(比如釋出到自己的網站或第三方)
  • 需要設計開放型API,便於其他第三方也能接入氣象站獲取資料
  • 提供溫度、氣壓和溼度的介面
  • 測量資料更新時,要能實時的通知給第三方

傳統方案:

  • 通過對需求的分析,我們可以設計一個WeatherData類,其中包含getXxx()方法,可以讓第三方接入,並得到相關資訊

  • 當有資料更新時,WeatherData呼叫dataChange()方法去更新資料,當第三方獲取時,就能獲取到最新的資料,當然也可以推送

  • WeatherData內部還需維護一個CurrentCondition物件,便於完成推送或通知

  • UML類圖

  • 程式碼實現

    • public class CurrentCondition {
      
         // 被通知的物件類
      
         // 溫度
         private double temperature;
         // 壓力
         private double pressure;
         // 溼度
         private double humidity;
      
         public void update(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             display();
         }
      
         private void display() {
             System.out.println("======CurrentCondition======");
             System.out.println("溫度:" + this.temperature);
             System.out.println("壓力:" + this.pressure);
             System.out.println("溼度:" + this.humidity);
         }
      
      }
      
    • public class WeatherData {
      
         // 天氣資料類
      
         // 溫度
         private double temperature;
         // 壓力
         private double pressure;
         // 溼度
         private double humidity;
      
         // 聚合天氣資料發生改變需要通知或者被推送的類
         private CurrentCondition currentCondition;
      
         public WeatherData(CurrentCondition currentCondition) {
             this.currentCondition = currentCondition;
         }
      
         // 將天氣資料的改變通知給需要通知或者被推送的類 被通知的類也可以通過getXxx()方法自己獲取資料
         private void dataChange() {
             currentCondition.update(this.temperature, this.pressure, this.humidity);
         }
      
         // 更改天氣資料
         public void setData(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             dataChange();
         }
      
      }
      
    • public class Client {
         public static void main(String[] args) {
             WeatherData weatherData = new WeatherData(new CurrentCondition());
             // 更新天氣資料 就會通知應用或者第三方
             weatherData.setData(10d, 20d, 30d);
             System.out.println("======天氣情況發生變換======");
             weatherData.setData(20d, 30d, 40d);
         }
      }
      
    • 問題分析

      • 其他第三方接入氣象站獲取資料問題
      • 無法在執行時動態的新增第三方
      • 違反了ocp原則,在WeatherData中,如果需要增加新的第三方應用,都需要建立一個對應的第三方的公告板物件,並加入到dataChange()方法中,不利於維護,也不是動態加入

基本介紹:

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

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

  • 一個物件(目標物件)的狀態發生改變,所有的依賴物件(觀察者物件)都將得到通知,進行廣播通知

  • 基本原理

    • 觀察者模式類似訂牛奶業務 例如:奶站/氣象局:Subject 和 使用者/第三方網站:Observer
    • Subject:登記註冊、移除和通知 一的一方(被觀察者)
      • registerObserver 註冊
      • removeObserver 移除
      • notifyObservers() 通知所有的註冊的使用者,根據不同需求,可以是更新資料,讓使用者來取,也可能是實施推送,看具體需求定
    • Observer:接收輸入 也就是第三方,可以有多個實現 多個一方(觀察者)
    • 物件之間多對一依賴的一種設計方案,被依賴的物件為Subject,依賴的物件為Observer,Subject通知Observer變化,比如這裡的奶站是Subject,是1的一方。使用者時Observer,是多的一方
  • UML類圖(案例)

  • 程式碼實現

    • public interface Subject {
      
         // 被觀察者介面 作為一的一方 ,需要聚合多個觀察者 可以使用List進行管理
      
         // 註冊觀察者
         void register(Observer observer);
      
         // 移除觀察者
         void remove(Observer observer);
      
         // 被觀察者狀態發生改變,進行通知所有的觀察者
         void notifyObservers();
      
      }
      
      // 實現類
      class WeatherData implements Subject {
      
         // 天氣資料類 作為被觀察者 一的一方
      
         // 溫度
         private double temperature;
         // 壓力
         private double pressure;
         // 溼度
         private double humidity;
      
         // 聚合所有的觀察者進行管理 動態進行修改 使用List集合管理觀察者們
         private List<Observer> observers;
      
         public WeatherData() {
             this.observers = new ArrayList<Observer>();
         }
      
         // 更改天氣資料
         public void setData(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             // 被觀察者狀態發生改變,通知註冊的所有觀察者
             notifyObservers();
         }
      
         @Override
         public void register(Observer observer) {
             if (!this.observers.contains(observer)) {
                 observers.add(observer);
             }
         }
      
         @Override
         public void remove(Observer observer) {
             this.observers.remove(observer);
         }
      
         @Override
         public void notifyObservers() {
             // 遍歷觀察者的集合 對所有的觀察者進行通知
             observers.forEach(observer -> observer.update(this.temperature, this.pressure, this.humidity));
         }
      
      }
      
      
    • public interface Observer {
      
         // 觀察者介面 作為多的一方
      
         // 被觀察者改變狀態時,通知觀察者所呼叫的方法
         void update(double temperature, double pressure, double humidity);
      
      }
      
      // 子類一
      class CurrentCondition implements Observer{
      
         // 被通知的物件類
      
         // 溫度
         private double temperature;
         // 壓力
         private double pressure;
         // 溼度
         private double humidity;
      
         public void update(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             display();
         }
      
         private void display() {
             System.out.println("======CurrentCondition======");
             System.out.println("溫度:" + this.temperature);
             System.out.println("壓力:" + this.pressure);
             System.out.println("溼度:" + this.humidity);
         }
      
      }
      
      // 子類二
      class BaiDu implements Observer{
      
         // 被通知的物件類
      
         // 溫度
         private double temperature;
         // 壓力
         private double pressure;
         // 溼度
         private double humidity;
      
         public void update(double temperature, double pressure, double humidity) {
             this.temperature = temperature;
             this.pressure = pressure;
             this.humidity = humidity;
             display();
         }
      
         private void display() {
             System.out.println("======BaiDu======");
             System.out.println("溫度:" + this.temperature);
             System.out.println("壓力:" + this.pressure);
             System.out.println("溼度:" + this.humidity);
         }
      
      }
      
    • public class Client {
         public static void main(String[] args) {
             // 建立觀察者
             CurrentCondition currentCondition = new CurrentCondition();
             // 建立被觀察者
             WeatherData weatherData = new WeatherData();
             // 向被觀察者中註冊觀察者
             weatherData.register(currentCondition);
             // 改變被觀察者的狀態 觀察者就會收到通知
             weatherData.setData(10d, 20d, 30d);
      
             // 後續業務擴充套件 加入新的第三方
             BaiDu baiDu = new BaiDu();
             weatherData.register(baiDu);
             System.out.println("======加入了新的第三方======");
             weatherData.setData(20d, 30d, 40d);
         }
      }
      

jdk原始碼:

  • 在jdk的Observer類和Observable類就使用到了觀察者模式

  • Observer作為了觀察者介面,提供了update()方法

  • Observable相當於Subject,作為被觀察者,只不過在jdk原始碼中,Observable是一個類,而不是介面,提供了註冊觀察者,刪除觀察者,通知觀察者的一系列管理觀察者的方法

  • // 觀察者介面
    public interface Observer {
       /**
        * This method is called whenever the observed object is changed. An
        * application calls an <tt>Observable</tt> object's
        * <code>notifyObservers</code> method to have all the object's
        * observers notified of the change.
        *
        * @param   o     the observable object.
        * @param   arg   an argument passed to the <code>notifyObservers</code>
        *                 method.
        */
       void update(Observable o, Object arg);
    }
    
    // 被觀察者 相當於Subject
    public class Observable {
       private boolean changed = false;
       private Vector<Observer> obs;
    
       /** Construct an Observable with zero Observers. */
    
       public synchronized void addObserver(Observer o) {
           if (o == null)
               throw new NullPointerException();
           if (!obs.contains(o)) {
               obs.addElement(o);
           }
       }
    
       /**
        * Deletes an observer from the set of observers of this object.
        * Passing <CODE>null</CODE> to this method will have no effect.
        * @param   o   the observer to be deleted.
        */
       public synchronized void deleteObserver(Observer o) {
           obs.removeElement(o);
       }
    
       /**
        * If this object has changed, as indicated by the
        * <code>hasChanged</code> method, then notify all of its observers
        * and then call the <code>clearChanged</code> method to
        * indicate that this object has no longer changed.
        * <p>
        * Each observer has its <code>update</code> method called with two
        * arguments: this observable object and <code>null</code>. In other
        * words, this method is equivalent to:
        * <blockquote><tt>
        * notifyObservers(null)</tt></blockquote>
        *
        * @see     java.util.Observable#clearChanged()
        * @see     java.util.Observable#hasChanged()
        * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
        */
       public void notifyObservers() {
           notifyObservers(null);
       }
    
       /**
        * If this object has changed, as indicated by the
        * <code>hasChanged</code> method, then notify all of its observers
        * and then call the <code>clearChanged</code> method to indicate
        * that this object has no longer changed.
        * <p>
        * Each observer has its <code>update</code> method called with two
        * arguments: this observable object and the <code>arg</code> argument.
        *
        * @param   arg   any object.
        * @see     java.util.Observable#clearChanged()
        * @see     java.util.Observable#hasChanged()
        * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
        */
       public void notifyObservers(Object arg) {
           /*
            * a temporary array buffer, used as a snapshot of the state of
            * current Observers.
            */
           Object[] arrLocal;
    
           synchronized (this) {
               /* We don't want the Observer doing callbacks into
                * arbitrary code while holding its own Monitor.
                * The code where we extract each Observable from
                * the Vector and store the state of the Observer
                * needs synchronization, but notifying observers
                * does not (should not).  The worst result of any
                * potential race-condition here is that:
                * 1) a newly-added Observer will miss a
                *   notification in progress
                * 2) a recently unregistered Observer will be
                *   wrongly notified when it doesn't care
                */
               if (!changed)
                   return;
               arrLocal = obs.toArray();
               clearChanged();
           }
    
           for (int i = arrLocal.length-1; i>=0; i--)
               ((Observer)arrLocal[i]).update(this, arg);
       }
    }
    

注意事項:

  • 觀察者和被觀察者是抽象耦合的,建立一套觸發機制
  • 如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間
  • 如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰
  • 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化
  • JAVA中已經有了對觀察者模式的支援類
  • 避免迴圈引用
  • 如果順序執行,某一觀察者錯誤會導致系統卡殼,一般採用非同步方式

相關文章