從一個氣象監測應用說起:
我們想做一個氣象應用,有一個WeatherData對象,該對象由氣象站的各種感應裝置提供溫度,溼度,氣壓數據 來初始化。同時我們還要設立3個佈告板,來根據天氣數據 顯示不同的內容。分別是:目前狀況,氣象統計,簡單的預報。 同時我們還希望可以擴展該接口,即第三方可以根據數據來實現自己的佈告板。
背景已經交代清楚了,這其實是一個一對多的關係,即多個佈告板全都依賴WeatherData對象,根據WeatherData對象做出自己的改變。下面是一個錯誤的示範
class WeatherData{
float temp;
float humidity;
float pressure;
WeatherData(float temp,float humidity,float pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
}
public void measurementsChanged(){
temp = getTemp();
humidity = getHumidity();
pressure = getPressure();
currentDonditionsDisplay.update(temp,humidity,pressure);
staticsDisplay.update(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure);
}
}
雖然它能夠滿足大部分條件,但是它不具備擴展性,每次添加新的佈告板時,我們都需要修改代碼,我們是針對接口編程而不是針對具體實現編程,佈告板沒有實現一個共同的接口,而且我們無法在運行的時候動態的刪除或添加布告板(只能在measurementsChanged()方法中刪除對該佈告板的更新。所以爲了搞定這個問題,我們引出觀察者模式:
認識觀察者模式:
觀察者模式和報紙和雜誌的訂閱很像,報社出版報紙,向某家報社訂閱報紙,只要他們有新報紙出版,就會給你送來,只要你是他們的訂戶,你就會一直收到新報紙。當你不想在看報紙時,取消訂閱,他們便不會再送新報紙給你。只要報社還在運營,就會一直有人向他們訂閱報紙或取消訂閱報紙。
出版者 + 訂閱者 = 觀察者模式
我們可以將他們改下名稱, 出版者改爲“主題”,訂閱者改爲 “觀察者”(《Head First 設計模式》裏面是這樣子的)。
觀察者模式的定義:
觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都將會收到通知並自動更新。
好了,我們可以根據這個模式來解決我們的實際問題了。
在我們的問題中,佈告板會隨着WeatherData的變化而得到更新,所以很明顯WeatherData是主題,而佈告板是觀察者。實現觀察者模式的方法不只一種,但是以包含Subjec與Observer接口的最爲常見,之後會講到JAVA中自帶的觀察者模式。下面是具體實現:
import java.util.*;
interface Subject {
void registerObserver(Observer o);
void removerObserver(Observer o);
void notifyObservers();
}
interface Observer {
void update(float temp, float humidity, float pressure);
}
interface Displayment {// 展示接口
void display();
}
class WeatherData implements Subject {
private float temp;
private float humidity;
private float pressure;
private List<Observer> observers = new ArrayList<Observer>();
WeatherData(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
}
@Override
public void registerObserver(Observer o) {
// TODO 自動生成的方法存根
observers.add(o);
}
@Override
public void removerObserver(Observer o) {
// TODO 自動生成的方法存根
int i = observers.indexOf(o);
if (i >= 0)
observers.remove(i);
}
@Override
public void notifyObservers() {
// TODO 自動生成的方法存根
for (Observer o : observers)
o.update(temp, humidity, pressure);
}
public void measurementsChanged(float temp, float humidity, float pressure) {
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
class CurrentConditionDisplay implements Observer, Displayment {
private float temp;
private float humidity;
private float pressure;
private Subject subject;
public CurrentConditionDisplay(Subject subject) {
// TODO 自動生成的構造函數存根
this.subject = subject;
subject.registerObserver(this);
}
public CurrentConditionDisplay() {
// TODO 自動生成的構造函數存根
}
@Override
public void update(float temp, float humidity, float pressure) {
// TODO 自動生成的方法存根
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
@Override
public void display() {
// TODO 自動生成的方法存根
System.out.println("當前氣溫:" + temp + "當前溼度:" + humidity + "當前氣壓:"
+ pressure);
}
}
public class WeatherDataTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData(27.2f, 30.0f, 110f);
weatherData.registerObserver(new CurrentConditionDisplay());
weatherData.measurementsChanged(24.0f, 22.2f, 23.2f);
}
}
此實現有缺點。一個觀察者可以有多個主題,觀察者可根據主題來實現不同的update(),例如我們可以將update()改成update(Subject subject,Object obj)後面的是Object是主題給觀察者的數據對象。
其實在這裏觀察者得到數據的方法是由主題推送的,觀察者無法通過自身得到更新的數據,我們可以在主題中實現觀察者訪問數據的接口
//在主題中提供數據接口
public float getTemp(){
return temp;
}
public float getHumiditu(){
return humidity;
}
public float getPressure(){
return pressure;
}
接下來我們用JAVA內置的支持來實現這個問題。
java內置的觀察者模式:
JAVA API有內置的觀察者模式,在java.util包中,包含最基本的Observer接口和Observable類(主題的另一種說法,“可觀察者”),裏面的方法請參考JDK文檔,在此我們就直接實現:
import java.util.*;
interface Displayment{//展示接口
void display();
}
class WeatherData extends Observable{
private float temp;
private float humidity;
private float pressure;
public WeatherData(){};
public void measurementsChanged(float temp, float humidity, float pressure){
this.temp = temp;
this.humidity = humidity;
this.pressure = pressure;
setChanged();//java自帶的,讓我們更新的時候有更多的彈性。
notifyObservers();
}
public float getTemp(){
return temp;
}
public float getHumidity(){
return humidity;
}
public float getPressure(){
return pressure;
}
}
class CurrentConditionDisplay implements Observer,Displayment{
private float temp;
private float humidity;
private float pressure;
private Observable observable;
public CurrentConditionDisplay(Observable observable) {
// TODO 自動生成的構造函數存根
this.observable = observable;
observable.addObserver(this);
}
public void display() {
// TODO 自動生成的方法存根
System.out.println("當前氣溫:"+temp+"當前溼度:"+humidity+"當前氣壓:"+pressure);
}
@Override
public void update(Observable o, Object data) {
// TODO 自動生成的方法存根
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData)o;
this.temp = weatherData.getTemp();
this.humidity = weatherData.getHumidity();
this.pressure = weatherData.getPressure();
display();
}
}
}
public class WeatherDataTest{
public static void main(String[] args){
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay display = new CurrentConditionDisplay(weatherData);
weatherData.measurementsChanged(11.0f, 1.2f, 11);
}
}
java.util.Observable的黑暗面:
可能你也注意到了,Observable是一個類,而不是一個接口,而且它自己也沒有實現一個接口,因此在很多方面限制了它的使用與複用,不符合我們設計原則,針對接口編程而不是針對實現編程。
首先Observable是一個類,你必須設計一個類繼承它,如果某類想同時具有另一個超類的行爲,則會陷入兩難,因爲JAVAv不支持多繼承。再者,Observable中的setChanged()方法是protected的,這就意味着除非你繼承自Observable,否則你無法創建Observable實例並組合到你自己的對象裏來。