#### **一、觀察者模式簡單實現(一)之追劇**
> 這里舉一個追劇的例子,平常為了不錯過最新的電視劇我們會訂閱或關注這個電視劇,當電視劇更新后會第一時間推送給我們,下來就簡單實現一下。
>[info] 注意,主題中的方法可能與我們前面的圖示中的不一樣,當然具體問題具體分析。
* **抽象觀察者類(Observer)**
~~~
/**
* 抽象觀察者類,為所有具體觀察者定義一個接口,在得到通知時更新自己
*/
public interface Observer {
/**
* 有更新
*
* @param message 消息
*/
public void update(String message);
}
~~~
* **抽象被觀察者(抽象主題)**
~~~
/**
* 抽象被觀察者類
*/
public interface Observable {
/**
* 推送消息
*
* @param message 內容
*/
void push(String message);
/**
* 訂閱
*
* @param observer 訂閱者
*/
void register(Observer observer);
}
~~~
* **具體的觀察者類(ConcreteObserver)**
~~~
/**
* 具體的觀察者類,也就是訂閱者
*/
public class User implements Observer {
@Override
public void update(String message) {
System.out.println(name + "," + message + "更新了!");
}
// 訂閱者的名字
private String name;
public User(String name) {
this.name = name;
}
}
~~~
* **具體的被觀察者類(具體主題)**
~~~
/**
* 具體的被觀察者類,也就是訂閱的節目
*/
public class Teleplay implements Observable{
private List<Observer> list = new ArrayList<Observer>();//儲存訂閱者
@Override
public void push(String message) {
for(Observer observer:list){
observer.update(message);
}
}
@Override
public void register(Observer observer) {
list.add(observer);
}
}
~~~
* **實現**
~~~
public class Client {
public static void main(String[] args) {
//被觀察者,這里就是用戶訂閱的電視劇
Teleplay teleplay = new Teleplay();
//觀察者,這里就是訂閱用戶
User user1 = new User("小明");
User user2 = new User("小光");
User user3 = new User("小蘭");
//訂閱
teleplay.register(user1);
teleplay.register(user2);
teleplay.register(user3);
//推送新消息
teleplay.push("xxx電視劇");
}
}
~~~
* **結果**
~~~
小明,xxx電視劇更新了!
小光,xxx電視劇更新了!
小蘭,xxx電視劇更新了!
~~~
**總結**:由上面的代碼可以看出實現了一對多的消息推送,推送消息都是依賴Observer和Observable這些抽象類,而User和Teleplay完全沒有耦合,保證了訂閱系統的靈活性和可擴展性。
**觀察者模式主要的作用就是對象解耦,將觀察者和被觀察者完全隔離,只依賴于Observer和Observable抽象。**
#### **二、簡單實現(二)之氣象監測應用**
:-: 
圖1 氣象站
如圖所示:系統分為3部分,**氣象站**(獲取是極其想的物理裝置)、**WeatherData對象**(追蹤來自氣象站的數據,并更新布告板)、**布告板**(顯示目前天氣狀況給用戶看)。
**WeatherData對象是主題,布告板是觀察者。有3個使用天氣數據的布告板:目前狀態布告板、氣象統計布告板、天氣預報布告板。一旦WeatherData有更新,這些布告板必須馬上更新。此系統還可以擴展,讓開發人員建立定制的布告板。**
:-: 
圖2 氣象站具體設計
##### **具體實現**
**Subject.java(抽象主題)**
~~~
package headfirst.observer.weather;
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
~~~
**Observer.java(抽象觀察者)**
~~~
package headfirst.observer.weather;
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
~~~
**DisplayElement.java**
~~~
package headfirst.observer.weather;
public interface DisplayElement {
public void display();
}
~~~
**氣象站(具體主題)**
**WeatherData.java**
~~~
package headfirst.observer.weather;
import java.util.*;
public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList();
}
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 (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer)observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
// other WeatherData methods here
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
~~~
**布告板(具體觀察者)**:具體的布告板
**目前狀況:CurrentConditionsDisplay.java**
~~~
package headfirst.observer.weather;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
~~~
**氣象統計:StatisticsDisplay.java**
~~~
package headfirst.observer.weather;
import java.util.*;
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum= 0.0f;
private int numReadings;
private WeatherData weatherData;
public StatisticsDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
~~~
**天氣預報:ForecastDisplay.java**
~~~
package headfirst.observer.weather;
import java.util.*;
public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
private WeatherData weatherData;
public ForecastDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}
}
~~~
**測試程序**
~~~
package headfirst.observer.weather;
import java.util.*;
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
~~~
輸出結果如下:
:-: 
圖3 氣象站輸出結果
如果還需要增加酷熱指數布告板,可以如下
**酷熱指數:HeatIndexDisplay.java**
~~~
package headfirst.observer.weather;
public class HeatIndexDisplay implements Observer, DisplayElement {
float heatIndex = 0.0f;
private WeatherData weatherData;
public HeatIndexDisplay(WeatherData weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(float t, float rh, float pressure) {
heatIndex = computeHeatIndex(t, rh);
display();
}
private float computeHeatIndex(float t, float rh) {
float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh)
+ (0.00941695 * (t * t)) + (0.00728898 * (rh * rh))
+ (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
(0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 *
(rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) +
(0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
0.000000000843296 * (t * t * rh * rh * rh)) -
(0.0000000000481975 * (t * t * t * rh * rh * rh)));
return index;
}
public void display() {
System.out.println("Heat index is " + heatIndex);
}
}
~~~
測試(增加酷熱指數布告板)
**WeatherStationHeatIndex.java**
~~~
package headfirst.observer.weather;
import java.util.*;
public class WeatherStationHeatIndex {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
~~~
輸出結果
:-: 
圖4 氣象站輸出結果(增加酷熱指數)
**二、1、簡單實現(二)之氣象監測應用(使用Java內置的觀察者模式來實現氣象站獲取數據并實時更新布告板的實例)**
修改后的氣象站OO設計如下
:-: 
圖5 修改后的氣象站OO設計(使用java內置的觀察者)
**主題(被觀察者)**
**WeatherData.java**
**WeatherData(主題)現在擴展自Observable(被觀察者),setChanged()方法用來標記已經改變的事實,好讓notifyObservers()知道它被調用時應該更新觀察者,如果notifyObservers()之前沒有先調用setChanged()方法,觀察者就不會被通知。setChanged()方法可以讓你在更新觀察者時,有更多的彈性,可以適當地通知觀察者。另外clearChanged()方法、hasChanged()方法有時也需要被用到。**
~~~
package headfirst.observer.weatherobservable;
import java.util.Observable;
import java.util.Observer;
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public WeatherData() { }
public void measurementsChanged() {
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;
}
}
~~~
**DisplayElement.java**
~~~
package headfirst.observer.weatherobservable;
public interface DisplayElement {
public void display();
}
~~~
**觀察者**
**布告板(具體觀察者)**
**目前狀況:CurrentConditionsDisplay.java**
~~~
package headfirst.observer.weatherobservable;
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData)obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
~~~
**氣象統計:StatisticsDisplay.java**
~~~
package headfirst.observer.weatherobservable;
import java.util.Observable;
import java.util.Observer;
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum= 0.0f;
private int numReadings;
public StatisticsDisplay(Observable observable) {
observable.addObserver(this);
}
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
float temp = weatherData.getTemperature();
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
}
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
+ "/" + maxTemp + "/" + minTemp);
}
}
~~~
**天氣預報:ForecastDisplay.java**
~~~
package headfirst.observer.weatherobservable;
import java.util.Observable;
import java.util.Observer;
public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
public ForecastDisplay(Observable observable) {
observable.addObserver(this);
}
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
lastPressure = currentPressure;
currentPressure = weatherData.getPressure();
display();
}
}
public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}
}
~~~
**酷熱指數:HeatIndexDisplay.java**
~~~
package headfirst.observer.weatherobservable;
import java.util.Observable;
import java.util.Observer;
public class HeatIndexDisplay implements Observer, DisplayElement {
float heatIndex = 0.0f;
public HeatIndexDisplay(Observable observable) {
observable.addObserver(this);
}
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
float t = weatherData.getTemperature();
float rh = weatherData.getHumidity();
heatIndex = (float)
(
(16.923 + (0.185212 * t)) +
(5.37941 * rh) -
(0.100254 * t * rh) +
(0.00941695 * (t * t)) +
(0.00728898 * (rh * rh)) +
(0.000345372 * (t * t * rh)) -
(0.000814971 * (t * rh * rh)) +
(0.0000102102 * (t * t * rh * rh)) -
(0.000038646 * (t * t * t)) +
(0.0000291583 * (rh * rh * rh)) +
(0.00000142721 * (t * t * t * rh)) +
(0.000000197483 * (t * rh * rh * rh)) -
(0.0000000218429 * (t * t * t * rh * rh)) +
(0.000000000843296 * (t * t * rh * rh * rh)) -
(0.0000000000481975 * (t * t * t * rh * rh * rh)));
display();
}
}
public void display() {
System.out.println("Heat index is " + heatIndex);
}
}
~~~
測試
**WeatherStation.java**
~~~
package headfirst.observer.weatherobservable;
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
~~~
輸出結果
:-: 
圖6 氣象站輸出結果(使用Java內置的觀察者模式)
增加酷熱指數布告板的測試
**WeatherStationHeatIndex.java**
~~~
package headfirst.observer.weatherobservable;
public class WeatherStationHeatIndex {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditions = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
~~~
從以上的圖3和圖6的結果對比來看,差別是文字輸出的次序不一樣了。原因在于Observable實現了它的notifyObservers()方法,導致了通知觀察者的次序不同于先前的次序,盡管誰也沒有錯,但是如果我們的代碼依賴這樣的次序就是錯的,因為一旦觀察者/可觀察者的實現有所改變,通知次序就會改變,很可能就會產生錯誤的結果。這不是我們所認為的松耦合。
#### **java.util.Observable的缺點**
* **Observable(被觀察者)是一個類**
**Observable是一個類,不是一個接口,甚至沒有實現一個接口**,如果想同時具有Observable類和另一個類的行為,就陷入兩難,因為Java不支持多繼承。再者,沒有Observable接口,所以無法建立自己的實現,和Java內置的Observe API搭配使用,也無法將java.util的實現換成另一套做法的實現。
* **Observable將關鍵的方法保護起來**
觀察Observable的API,可以發現setChanged()方法被保護起來,權限修飾符是protected,意味著:除非繼承自Observable,否則你無法創建Observable實例并組合到你自己的對象中。這設計違反了第二個設計原則:“多用組合,少用繼承”。