觀察者模式

什么是觀察者模式?
定義了對象之間的一對多依賴,這樣一來,當(dāng)一個對象改變狀態(tài)時,它的所有依賴者都會收到通知并自動更新。觀察者模式定義了一系列對象之間的一對多關(guān)系。

案例(氣象站)
氣象站建立在WeatherData對象上,由WeatherData對象負(fù)責(zé)追蹤目前的天氣狀況(溫度、溫度、氣壓)。建立三種布告板,分別顯示目前的狀況、氣象統(tǒng)計以及簡單的預(yù)報。當(dāng)WeatherData獲得到新的測量數(shù)據(jù)時候,三種布告板必須實(shí)時更新。


觀察者示意圖.jpg

WeatherData從氣象站追蹤溫度,濕度,氣壓這一部分工作不需要我們考慮。
我們只需要建立WeatherData和布告板們之間的關(guān)系。
首先先來看看WeatherData準(zhǔn)備了什么?

public class WeatherData {

    private float temperature;
    private float humidity;
    private float pressure;
    //get方法獲取數(shù)據(jù)
    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    //當(dāng)氣象測量數(shù)據(jù)更新的時候,此方法會被調(diào)用
    public void measurementsChanged() {

    }
}

在使用觀察者模式解決上述案例之前,先來了解一下觀察者模式。
舉個例子(??)
報紙的訂閱。


觀察值模式——報紙訂閱.jpg

報社定期收集事件并出版,報紙會送給訂閱該報社的用戶們??梢杂行碌挠脩粲嗛唸笊?,用戶也可以取消訂閱。取消訂閱后,報社有新報發(fā)版的時候?qū)⒉粫托聢蠹垇怼?/p>

出版者(主題)+訂閱者們(觀察者們)=觀察者模式
當(dāng)主題發(fā)生改變的時候,就會通知該主題的觀察者數(shù)據(jù)已經(jīng)更新。

再舉個例子(??)
博主和粉絲的關(guān)系。


觀察者模式——博主:粉絲.jpg

從這個案例分析,博主就是主題,而粉絲就是觀察者們,當(dāng)博主發(fā)微博后,只要關(guān)注,也就是訂閱他的粉絲們都會收到該博主的更新的博文??梢孕略龇劢z,老粉也可以取關(guān)。從這個案例也可以看出,觀察者模式重點(diǎn)是主題和觀察者們之前的關(guān)系,主題是怎樣獲取數(shù)據(jù),觀察者模式并不關(guān)心。其中新粉和老粉取關(guān)不會影響到博主,這一現(xiàn)象也正是松耦合的威力。主題不需要知道觀察者的具體類是誰,做了什么事情。主題唯一以來的東西是一個實(shí)現(xiàn)Observer接口的對象列表。改變主題或者觀察者其中一方,并不會影響另一方,因?yàn)閮烧呤撬神詈稀?/p>

進(jìn)階:通過微博博主和粉絲的案例。這個博主肯定也會關(guān)注其他的博主,而粉絲們當(dāng)然也會被其他人關(guān)注著。這也就是說一個對象可以有兩張身份,既可以是主題,也可以是觀察者。但要注意在一個觀察者模式中,主題和觀察者模式是一對多的關(guān)系。

既然搞清楚了觀察者的概念,現(xiàn)在就用它來實(shí)現(xiàn)一開始的氣象站案例吧。
設(shè)計氣象站


觀察者模式之氣象站uml圖.png

定義Subject 接口

public interface Subject {

    //注冊一個觀察者
    public void registerObserver(Observer o);

    //移除一個觀察者
    public void removeObserver(Observer o);

    //當(dāng)主題狀態(tài)改變時,該方法被調(diào)用,以通知所有的觀察者
    public void notifyObserver();
}

定義Observer接口

public interface Observer {

    //所有的觀察者都必須實(shí)現(xiàn)該方法;當(dāng)氣象觀測值改變時,主題會把這些狀態(tài)值當(dāng)作方法的參數(shù)
    public void update(float temp, float humidity, float pressure);
}

定義該案例特有的顯示接口

public interface DisplayElement {

    //當(dāng)布告板需要顯示時,調(diào)用該方法
    public void display();
}

實(shí)現(xiàn)WeatherData

public class WeatherData implements Subject {

    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<>();
    }

    @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 notifyObserver() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    //當(dāng)主題狀態(tài)改變時,該方法被調(diào)用,以通知所有的觀察者
    public void measurementsChanged() {
        notifyObserver();
    }

    //主題的狀態(tài)改變,數(shù)值都變
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

建立CurrentConditionDisplay布告板(省略另外兩個布告板)

//目前狀況布告板 是觀察者,也要展示
public class CurrentConditionDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private Subject weatherData;

    //在構(gòu)造器中要定義這個觀察者依賴的主題,還要在這個主題下面注冊自己
    public CurrentConditionDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Current conditions :" + temperature + "F degrees and " + humidity + "% humidity");
    }

    //更新變量并且實(shí)時暫display
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        display();
    }
}

測試

public class WeatherStation {

    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        weatherData.setMeasurements(20f, 30f, 40f);
    }
}

測試結(jié)果

Current conditions :20.0F degrees and 30.0% humidity
Statistics conditions :20.0F degrees and 30.0% humidity40.0壓力
Forecast conditions :20.0F degrees and 30.0% humidity40.0壓力

上訴案例看起來很完美,但其實(shí)也有點(diǎn)小問題。
問題一:Observer接口的參數(shù)定死了,這樣一點(diǎn)都不彈性,以后要加入其它的變量怎么辦。
問題二:當(dāng)主題改變時,會一股腦將數(shù)據(jù)都給觀察者,但其實(shí)觀察者不需要這么多數(shù)據(jù),它想要的只是主題中的一部分?jǐn)?shù)據(jù)。這種情況獲取,可以由觀察者去獲取自己想要的數(shù)據(jù)。

恰巧Java內(nèi)置的觀察者模式可以解決這兩個問題。是的,你沒聽錯,Java內(nèi)置了觀察者,這就說明觀察者這種模式很常用了吧。下面用Java自帶的觀察者模式解決下氣象站的案例。


Java內(nèi)置觀察者模式之氣象站uml圖.png

Java內(nèi)置的主題:java.util.Observable
Java內(nèi)置的觀察者:java.util.Observer

使用Java內(nèi)置的主題:

public class WeatherData extends Observable{
    private float temperature;
    private float humidity;
    private float pressure;
    
    //不需要觀察者的list的變量,也不需要再構(gòu)造器中建立觀察者的list,因?yàn)镺bservable已經(jīng)幫我們完成了
    public WeatherData() {
    }

    public void measurementsChanged(){
        //再調(diào)用通知觀察者們之前,先調(diào)用setChanged()表明狀態(tài)已經(jīng)變了。
        setChanged();
        notifyObservers();
    }

    public void setMeasurements(float temperature,float humidity, float pressure){
        this.temperature  = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

使用Java內(nèi)置的觀察者

public class CurrentConditionDisplay implements Observer,DisplayElement {
    private float temperature;
    private float humidity;
    private Observable weatherData;

    public CurrentConditionDisplay(Observable weatherData) {
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof  WeatherData){
            WeatherData weatherData = (WeatherData)o;
            //觀察者主動去主題拉數(shù)據(jù)
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }

    @Override
    public void display() {
        System.out.println("Current conditions :"+ temperature +"F degrees and " + humidity +"% humidity");
    }

}

測試

public class WeatherStation {

    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);


        weatherData.setMeasurements(20,30,40);

    }
}

測試結(jié)果,注意順序。

Statistics conditions :20.0F degrees and 30.0% humidity40.0壓力
Forecast conditions :20.0F degrees and 30.0% humidity40.0壓力
Current conditions :20.0F degrees and 30.0% humidity

順序不一致,所以不要依賴觀察者被通知的順序。為什么會這樣呢?查看源碼可知

public void notifyObservers(Object arg) {
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        //其實(shí)是這里的遍歷搞得鬼
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

詳解Observable
setChanged()方法是不是多此一舉?
當(dāng)然不是,比如氣象站的例子,氣候肯定是時時刻刻在變化,如果我們的硬件很靈敏,那么WeatherData也會跟著氣候一直跟著改變,那布告板不也實(shí)時更新。這樣是不是太多平凡了?所以setChanged()起到了作用,當(dāng)setChanged調(diào)用時候,才主題才會去通知觀察者數(shù)據(jù)改變了,而不是時時刻刻通知了。
Observable居然是一個類不是一個接口?
是的,Observable是一個類。這樣就限制了它的復(fù)用。只能繼承,而Java不支持多重繼承。Observable中也沒有接口,無法建立用戶的實(shí)現(xiàn)。但其實(shí)你也可以自己實(shí)現(xiàn)自己的Observable接口。

白話總結(jié)
一個主題掌握著數(shù)據(jù),一群觀察者想要獲得主題的數(shù)據(jù),于是這些觀察者就訂閱了這個主題,訂閱成功之后,當(dāng)主題掌握的數(shù)據(jù)改變了,那就會通知訂閱自己的觀察者數(shù)據(jù)改變了。觀察者有有種獲得數(shù)據(jù)的方式,一種是主題把所有數(shù)據(jù)都推給觀察者,另一張是觀察者自己去拉自己想要的數(shù)據(jù)。主題和觀察者可自定義也可以用Java中內(nèi)置的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容