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搭配使用。而且也違反了我們的設計原則:多用組合,少用繼承。
所以不管用哪種方法都可以實現觀察者模式,前提是根據我們的業務場景來。