[設計模式學習筆記] -- 觀察者模式

觀察者模式java

定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的全部依賴者都會收到通知並自動更新。算法

  出版者+訂閱者=觀察者模式。以報社與訂報人爲例:編程

  1. 報社出版報紙。
  2. 你向這家報社訂閱報紙,只要他們有新版報紙出版,就會給你送一份。
  3. 當你不想再看報紙的時候,取消訂閱,報社就不會在給你送報紙。

  舉個簡單的例子來描述觀察者模式。dom

  某某市要創建空氣質量站,購買了最新型的空氣質量監測儀來實時監測城市的空氣質量。並開放接口將空氣質量提供給全部須要的人或公司。大致結構以下:ide

 

  空氣質量站(AirQualityStations)知道如何採集空氣質量檢測儀監測到的數據,獲得數據後發送給PM2.5數據網(PM2d5Display)和空氣質量發佈網(AQIDisplay)。函數

  功能很簡單,首先看一段實現的代碼(錯誤的示範):測試

// 空氣質量數據更新。
// 空氣質量監測儀會自動調用該方法,並更新pollutants的數據。
public void AirQualityChanged() {
    // 將最新的空氣質量數據更新給這兩個網站。
    pm2d5Display.Update(pollutants);
    aqiDisplay.Update(pollutants);
}

  在這個實現中,犯了幾個錯誤:網站

  1. 針對具體實現編程,而非接口。
  2. 只支持兩個網站,若加入新網站要修改代碼。
  3. 沒法在運行時動態的增長或刪除網站。
  4. 爲封裝未來會改變的部分。

  若是使用個觀察者模式,將會很好的解決這些問題,觀察者模式以鬆耦合的方式定義了一系列對象之間的一對多關係。當一個對象改變狀態,其餘依賴者都會收到通知。this

設計原則spa

爲了交互對象之間的鬆耦合設計而努力。

  如今,使用觀察者模式設計這個需求,類圖以下:

  Subject爲主題接口,空氣質量監測站實現了主題接口。Observer爲觀察者接口,由要發佈空氣質量信息的網站所實現,同時也爲它們創建了一個顯示空氣信息的接口。代碼以下:

package cn.net.bysoft.observer;

//    主題接口。
public interface Subject {
    
    //    定義註冊或刪除觀察者的方法。
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    
    //    當主題狀態改變時,這個方法會被調用,以通知全部的觀察者。
    public void notifyObservers();
}
主題接口代碼
package cn.net.bysoft.observer;

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

//    空氣質量檢測站。
public class AirQualityStations implements Subject {

    public AirQualityStations() {
        this.observers = new ArrayList<Observer>();
    }

    // 添加觀察者。
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    // 刪除觀察者。
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    // 當空氣質量值改變時該函數會被調用。
    public void notifyObservers() {
        for (Observer o : observers) {
            //    更新觀察者的數據。
            o.update(this.pollutants);
        }
    }

    // 空氣質量數據更新。
    // 空氣質量監測儀會自動調用該方法,並更新pollutants的數據。
    public void AirQualityChanged() {
        notifyObservers();
    }

    public Pollutants getPollutants() {
        return pollutants;
    }
    
    public void setPollutants(Pollutants pollutants) {
        this.pollutants = pollutants;
    }

    // 污染物對象。
    private Pollutants pollutants;
    // 觀察者集合。
    List<Observer> observers;
}
空氣質量監測站代碼
package cn.net.bysoft.observer;

//    觀察者接口。
public interface Observer {
    //    當空氣質量值改變時,主題會把這些狀態值看成方法的參數,傳送給觀察者。
    public void update(Pollutants pollutants);
}
觀察者接口代碼
package cn.net.bysoft.observer;

//    顯示空氣質量接口。
public interface DisplayElement {
    public void display();
}
顯示空氣質量接口代碼
package cn.net.bysoft.observer;

// 污染物實體類。
public class Pollutants {
    //    細顆粒物。
    private int PM2d5;
    //    二氧化硫。
    private int SO2;
    //    二氧化氮。
    private int NO2;
    //    臭氧。
    private int O3;
    //    一氧化碳。
    private int CO;
    //    可吸入顆粒物。
    private int PM10;
    
    public int getPM2d5() {
        return PM2d5;
    }
    public void setPM2_5(int pM2d5) {
        PM2d5 = pM2d5;
    }
    public int getSO2() {
        return SO2;
    }
    public void setSO2(int sO2) {
        SO2 = sO2;
    }
    public int getNO2() {
        return NO2;
    }
    public void setNO2(int nO2) {
        NO2 = nO2;
    }
    public int getO3() {
        return O3;
    }
    public void setO3(int o3) {
        O3 = o3;
    }
    public int getCO() {
        return CO;
    }
    public void setCO(int cO) {
        CO = cO;
    }
    public int getPM10() {
        return PM10;
    }
    public void setPM10(int pM10) {
        PM10 = pM10;
    }
}
空氣中的污染物實體類代碼
package cn.net.bysoft.observer;

import java.util.Date;

//    PM2.5佈告板。
public class PM2d5Display implements Observer, DisplayElement {
    //    污染物。
    private Pollutants pollutants;
    
    //    顯示污染物。
    public void display() {
        System.out.println(new Date().toString());
        System.out.println("PM2.5的指數是" + pollutants.getPM2d5());
        System.out.println("來自 [www.哈哈哈網.com] ");
        System.out.println("========================================================");
    }

    public void update(Pollutants pollutants) {
        // TODO Auto-generated method stub
        this.pollutants = pollutants;
        this.display();
    }
}
觀察者A的代碼
package cn.net.bysoft.observer;

import java.util.Date;

//    空氣質量佈告板。
public class AQIDisplay implements Observer, DisplayElement {
    // 污染物。
    private Pollutants pollutants;

    public Pollutants getPollutants() {
        return pollutants;
    }

    public void setPollutants(Pollutants pollutants) {
        this.pollutants = pollutants;
    }

    public void display() {
        System.out.println(new Date().toString());
        System.out.println("通過AQI算法最後得出空氣質量爲:" + getAQI());
        System.out.println("來自 [www.嘿嘿嘿網.com] 的空氣指數數據");
        System.out.println("======================================================");
        System.out.println();
    }

    private String getAQI() {
        int num = this.pollutants.getPM2d5();
        String result = "";
        if (num < 25) {
            result = "優";
        } else if (num >= 25 && num < 50) {
            result = "良";
        } else if (num >= 50 && num < 57) {
            result = "輕度污染";
        } else
            result = "重度污染";
        return result;
    }

    public void update(Pollutants pollutants) {
        this.pollutants = pollutants;
        this.display();
    }
}
觀察者B的代碼

  編寫好這些類與接口後,進行測試,在建立2個類,一個是空氣質量監測儀(AirQualityApparatus),一個是空氣質量監測服務(AirQualityService,帶main函數),代碼以下:

package cn.net.bysoft.observer;

import java.util.Random;

//    空氣設備監測儀。
public class AirQualityApparatus {
    
    public AirQualityApparatus() {}
    
    public AirQualityApparatus(AirQualityStations airQualityStations)
    {
        this.airQualityStations = airQualityStations;
    }
    
    public void Start()
    {
        //    設置空氣質量。
        Pollutants pollutants = new Pollutants();
        pollutants.setPM2_5(getRandomNum());
        pollutants.setSO2(getRandomNum());
        pollutants.setNO2(getRandomNum());
        pollutants.setO3(getRandomNum());
        pollutants.setCO(getRandomNum());
        pollutants.setPM10(getRandomNum());
        
        airQualityStations.setPollutants(pollutants);
        airQualityStations.AirQualityChanged();
    }
    
    //    隨機生成1-100。
    private int getRandomNum()
    {
        Random r = new Random();
        int num = r.nextInt(100);
        return num + 1;
    }
    
    private AirQualityStations airQualityStations;
}
空氣設備監測儀代碼
package cn.net.bysoft.observer;

//    空氣質量服務中心。
public class AirQualityService {

    public static void main(String[] args) {
        //    創建一個空氣質量站。
        AirQualityStations airQualityStations = new AirQualityStations();
        //    創建一個空氣質量監測設備。
        AirQualityApparatus airQualityApparatus = new AirQualityApparatus(airQualityStations);

        //    添加須要空氣質量數據的網站。
        airQualityStations.registerObserver(new PM2d5Display());
        airQualityStations.registerObserver(new AQIDisplay());
        
        while(true)
        {
            airQualityApparatus.Start();
            
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
空氣質量服務中心代碼,main函數類

  運行結果如圖:

  Java內置了觀察者模式,與上面自定義的觀察者模式有一些小差別。主題爲java.util.Observable類,觀察者爲java.util.Observer接口。Observer接口只聲明瞭一個方法,即爲void update(Observable o, Object arg)。而Observable類則與上面的Subject有些不一樣。

  Observable類多出了一個setChanged()方法,用來標記觀察的目標狀態已經改變,好讓notifyObservers()方法知道當它被調用時應該更新觀察者。由於有時不須要某種數據只要改變就會發送消息給觀察者,好比溫度,咱們但願溫度上升一度在將消息發送給觀察者,而不是上升0.1度就發送。具體能夠看Observable類的源碼:

package java.util;

public class Observable {
    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();
    }

    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }

    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}
Observable源碼

  使用Java內置的觀察者修改一下上面的空氣質量監測站,代碼與測試結果以下:

package cn.net.bysoft.observer;

import java.util.Observable;

//    空氣質量監測站類,繼承了java內置的可觀察者類。
public class AirQualityStations extends Observable {
    
    // 空氣質量數據更新。
        // 空氣質量監測儀會自動調用該方法,並更新pollutants的數據。
        public void AirQualityChanged() {
            super.setChanged();
            //    沒有將數據傳遞給觀察者,觀察者必須本身到這個類中拉數據。
            super.notifyObservers();
        }
    
    public Pollutants getPollutants() {
        return pollutants;
    }
    
    public void setPollutants(Pollutants pollutants) {
        this.pollutants = pollutants;
    }

    // 污染物對象。
    private Pollutants pollutants;
}
空氣質量監測站的代碼
package cn.net.bysoft.observer;

import java.util.Date;
import java.util.Observable;
import java.util.Observer;

//    PM2.5佈告板。
public class PM2d5Display implements Observer, DisplayElement {
    //    污染物。
    private Pollutants pollutants;
    
    //    顯示污染物。
    public void display() {
        System.out.println(new Date().toString());
        System.out.println("PM2.5的指數是" + pollutants.getPM2d5());
        System.out.println("來自 [www.哈哈哈網.com] ");
        System.out.println("========================================================");
    }

    /*public void update(Pollutants pollutants) {
        // TODO Auto-generated method stub
        this.pollutants = pollutants;
        this.display();
    }*/
    
    //    實現java內置的觀察者接口的update方法。
    public void update(Observable o, Object arg) {
        // 得到被觀察者類,也就是空氣監測站。
        AirQualityStations airQualityStations = (AirQualityStations)o;
        this.pollutants = airQualityStations.getPollutants();
        this.display();
    }
}
觀察者類的代碼
package cn.net.bysoft.observer;

//    空氣質量服務中心。
public class AirQualityService {

    public static void main(String[] args) {
        // 創建一個空氣質量站。
        AirQualityStations airQualityStations = new AirQualityStations();
        // 創建一個空氣質量監測設備。
        AirQualityApparatus airQualityApparatus = new AirQualityApparatus(
                airQualityStations);

        // 添加須要空氣質量數據的網站。
        airQualityStations.addObserver(new PM2d5Display());
        airQualityStations.addObserver(new AQIDisplay());

        while (true) {
            airQualityApparatus.Start();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
修改後的main方法

  若是你想"推"(push)數據給觀察者,你能夠把數據看成數據對象傳送給notifyObservers(arg)方法,不然,觀察者就必須從可觀察者對象中"拉"(pull)數據,就像上訴代碼中的作法。注意,文字輸出的次序與以前的測試也不同了,由於咱們實現的是Observable類的notifyObservers()方法,這致使了通知觀察者的次序不一樣與以前的次序。

  最後,java內置的Observable也有黑暗面,首先它是一個類,而不是接口,而且它也沒有去實現任何一個接口。因此咱們必須設計一個類去繼承它。若是這個類想作被java內置的觀察者類的同時又想繼承某個父類,就會陷入兩難,畢竟Java不支持多繼承。這限制了Observable類的複用潛力。再者,由於Observable沒有接口,看Observable源碼中setChanged()方法被定義爲protected。這意味着除非你繼承自Observable,不然沒法建立Observable實例被組合到本身的對象中來。因此用哪一種方式合適須要斟酌,其實本身編寫觀察者模式也沒問題,畢竟它那麼簡單。

相關文章
相關標籤/搜索