1. 程式人生 > >Java設計模式之觀察者模式的兩種實現

Java設計模式之觀察者模式的兩種實現

     觀察者模式就是定義物件之間的一對多依賴,這樣一來,當一個物件狀態發生改變時,它的所有依賴者都會收到通知並自動更新。  這樣的好處就是兩個或多個物件之間鬆耦合,它們依然可以互動,但不太清楚彼此的細節。觀察者模式提供了一種物件的設計,讓主題和觀察者之間鬆耦合。鬆耦合的設計可以讓我們建立有彈性的OO系統,能夠應對變化,是因為物件之間的互相依賴降到了最低。

      現在我們用一個簡單的案例來熟悉觀察者模式是怎麼實現的。我們設計一個氣象站,氣象站提供天氣資料,天氣資料更新後,要實時顯示在公告板上。

我們分別用自定義的觀察者模式和Java內建的觀察者模式兩種方式來實現。首先是第一種。

定義主題介面,所有的主題都實現主題介面

package com.example.demo.observer.customize;

/**
 * 主題物件
 */
public interface Subject {

    /**
     * 新增觀察者
     * @param observer
     */
    public void addObserver(Observer observer);

    /**
     * 刪除指定觀察者
     * @param observer
     */
    public void deleteObserver(Observer observer);

    /**
     * 通知所有觀察者
     */
    public void notifyObservers();


}

實現我們的主題類,也就是我們的被觀察者

    package com.example.demo.observer.customize;
    
    import java.util.ArrayList;
    
    /**
     * 被觀察者物件,實現主題介面
     */
    public class WeatherData implements Subject {
    
         //溫度
        private float temperature;
        //溼度
        private float humidity;
        //氣壓
        private float airpressure;

        //觀察者列表
        private ArrayList<Observer> observerArrayList;
    
        public WeatherData() {
            this.observerArrayList = new ArrayList<Observer>();
        }
    
        /**
         * 新增指定觀察者物件
         * @param observer
         */
        @Override
        public void addObserver(Observer observer) {
            this.observerArrayList.add(observer);
        }
    
        /**
         * 刪除指定觀察者物件
         * @param observer
         */
        @Override
        public void deleteObserver(Observer observer) {
            int i;
            if((i = observerArrayList.indexOf(observer)) != -1) {
                this.observerArrayList.remove(i);
            }
        }
    
        /**
         * 通知觀察者
         */
        @Override
        public void notifyObservers() {
            for(Observer observer : this.observerArrayList) {
                observer.update(this.temperature, this.humidity, this.airpressure);
            }
        }
    
        /**
         * 被觀察者資料發生改變
         * @param temperature
         * @param humidity
         * @param airpressure
         */
        public void setMeasurements(float temperature, float humidity, float airpressure) {
            this.temperature = temperature;
            this.humidity = humidity;
            this.airpressure = airpressure;
            this.measurementsChanged();
        }
    
        /**
         * 修改後,通知觀察者
         */
        public void measurementsChanged() {
            this.notifyObservers();
        }
    }

定義我們的觀察者介面,所有的觀察者都實現此介面,稱為觀察者物件

package com.example.demo.observer.customize;

/**
 * 觀察者介面
 */
public interface Observer {

    /**
     * 呼叫觀察者者更新介面
     * @param temperature
     * @param humidity
     * @param airpressure
     */
    public void update(float temperature, float humidity, float airpressure);
}

定義一個公告板顯示介面,觀察者要實現此介面來顯示在公告板上

package com.example.demo.observer.customize;

public interface DisplayElement {

    public void display();
}

定義觀察者物件:觀察者物件,在初始化構造的時候,註冊主題(成為觀察主題的物件)

package com.example.demo.observer.customize;

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private float airpressure;
    private Subject subject;

    public CurrentConditionsDisplay(Subject subject) {
        this.subject = subject;
        this.subject.addObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float airpressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.airpressure = airpressure;
        this.display();
    }

    @Override
    public void display() {
        System.out.println("氣溫:"+ this.temperature+"\t"+"溼度:"+this.humidity+"\t"+"氣壓:"+this.airpressure);
    }
}
package com.example.demo.observer.customize;

public class ForecastDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private float airpressure;
    private Subject subject;

    public ForecastDisplay(Subject subject) {
        this.subject = subject;
        this.subject.addObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float airpressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.airpressure = airpressure;
        this.display();
    }

    @Override
    public void display() {
        System.out.println("氣溫:"+ this.temperature+"\t"+"溼度:"+this.humidity+"\t"+"氣壓:"+this.airpressure);
    }
}
package com.example.demo.observer.customize;

public class StatisticsDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private float airpressure;
    private Subject subject;

    public StatisticsDisplay(Subject subject) {
        this.subject = subject;
        this.subject.addObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float airpressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.airpressure = airpressure;
        this.display();
    }

    @Override
    public void display() {
        System.out.println("氣溫:"+ this.temperature+"\t"+"溼度:"+this.humidity+"\t"+"氣壓:"+this.airpressure);
    }
}

測試我們的觀察者模式

package com.example.demo.observer.customize;

public class WeatherMain {

    public static void main(String[] args) {

        //被觀察者
        WeatherData weatherData = new WeatherData();

        //觀察者物件
        CurrentConditionsDisplay conditionsDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        weatherData.setMeasurements(80, 85, 90);

    }
}

第二種實現方式,用Java的內建的API實現觀察者模式,主題物件繼承Observable類,觀察者物件實現Observer介面即可和以上方法類似。修改我們的主題物件。

package com.example.demo.observer.internal;

import java.util.Observable;

public class WeatherData extends Observable {

    private float temperature;
    private float humidity;
    private float airpressure;

    public WeatherData() {
    }

    /**
     * 被觀察者資料發生改變
     * @param temperature
     * @param humidity
     * @param airpressure
     */
    public void setMeasurements(float temperature, float humidity, float airpressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.airpressure = airpressure;
        this.measurementsChanged();
    }

    /**
     * 修改後,通知觀察者
     */
    public void measurementsChanged() {
        super.setChanged();
        super.notifyObservers();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getAirpressure() {
        return airpressure;
    }

}

Observable可以主動推(push)資料給觀察者物件,也可以讓觀察者物件拉(pull)資料。

package com.example.demo.observer.internal;

import com.example.demo.observer.customize.DisplayElement;

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

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private float airpressure;
    private Observable observable;

    public CurrentConditionsDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof WeatherData) {
            this.temperature = ((WeatherData) o).getTemperature();
            this.humidity = ((WeatherData) o).getHumidity();
            this.airpressure = ((WeatherData) o).getAirpressure();
            this.display();
        }
    }

    @Override
    public void display() {
        System.out.println("氣溫:"+ this.temperature+"\t"+"溼度:"+this.humidity+"\t"+"氣壓:"+this.airpressure);
    }
}
package com.example.demo.observer.internal;

import com.example.demo.observer.customize.DisplayElement;

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

public class ForecastDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private float airpressure;
    private Observable observeable;

    public ForecastDisplay(Observable observeable) {
        this.observeable = observeable;
        this.observeable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof WeatherData) {
            this.temperature = ((WeatherData) o).getTemperature();
            this.humidity = ((WeatherData) o).getHumidity();
            this.airpressure = ((WeatherData) o).getAirpressure();
            this.display();
        }
    }

    @Override
    public void display() {
        System.out.println("氣溫:"+ this.temperature+"\t"+"溼度:"+this.humidity+"\t"+"氣壓:"+this.airpressure);
    }

}
package com.example.demo.observer.internal;

import com.example.demo.observer.customize.DisplayElement;

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

public class StatisticsDisplay implements Observer, DisplayElement {

    private float temperature;
    private float humidity;
    private float airpressure;
    private Observable observable;

    public StatisticsDisplay(Observable observable) {
        this.observable = observable;
        this.observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof WeatherData) {
            this.temperature = ((WeatherData) o).getTemperature();
            this.humidity = ((WeatherData) o).getHumidity();
            this.airpressure = ((WeatherData) o).getAirpressure();
            this.display();
        }
    }

    @Override
    public void display() {
        System.out.println("氣溫:"+ this.temperature+"\t"+"溼度:"+this.humidity+"\t"+"氣壓:"+this.airpressure);
    }
}

測試方法同上。  和自定義觀察者模式的區別在於,通知觀察者前首先要呼叫setChanged()方法,修改主題物件的標識,該標識的好處是可以自由的控制主題物件和觀察者之間的互動,比如一些微小的資料修改不通知觀察者,就可以通過該方法來控制;還有觀察者update(Observable o, Object arg)介面的第一個引數Observable o是可以讓觀察者知道是哪個主題呼叫它,也可以通過該主題物件,去主題拉取資料。Object arg引數是主題物件推送過來的資料物件,觀察者直接可以獲取主題物件傳送的資料進行處理。

經過測試我們可以觀察到,通過Java內建的觀察者模式,我們發現Observalbe的notifyObservers()方法通知觀察者的時候和我們自定義觀察者模式的順序是不一樣的,但如果我們的程式碼依賴這樣的次序,這種實現就是錯的。通知次序的改變,很可能會產生錯誤的結果。

另一方面,Observable是一個類,並不是一個介面,它限制了我們的複用,也違反了我們的OO設計原則(針對介面程式設計,而非針對實現程式設計)。如果主題物件要想繼承其他超類,就會陷入兩難的境地,畢竟Java不支援多繼承,限制了Observable的複用潛力。再者,因為沒有Observable介面,所以也無法建立自己的實現,和Java內建Observer API搭配使用。而且也違反了我們的設計原則:多用組合,少用繼承。

所以不管用哪種方法都可以實現觀察者模式,前提是根據我們的業務場景來。