#### **一、观察者模式简单实现(一)之追剧**
> 这里举一个追剧的例子,平常为了不错过最新的电视剧我们会订阅或关注这个电视剧,当电视剧更新后会第一时间推送给我们,下来就简单实现一下。
>[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抽象。**
#### **二、简单实现(二)之气象监测应用**
:-: ![](https://box.kancloud.cn/382630a8883d40bf89a07ebaab918617_937x562.jpg)
图1 气象站
如图所示:系统分为3部分,**气象站**(获取是极其想的物理装置)、**WeatherData对象**(追踪来自气象站的数据,并更新布告板)、**布告板**(显示目前天气状况给用户看)。
**WeatherData对象是主题,布告板是观察者。有3个使用天气数据的布告板:目前状态布告板、气象统计布告板、天气预报布告板。一旦WeatherData有更新,这些布告板必须马上更新。此系统还可以扩展,让开发人员建立定制的布告板。**
:-: ![](https://box.kancloud.cn/3a09d9d237ec9a35584dcffbc87ccd2e_676x656.jpg)
图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);
}
}
~~~
输出结果如下:
:-: ![](https://box.kancloud.cn/af6c09a68cd5b8ac340c9b69270873a2_655x306.jpg)
图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);
}
}
~~~
输出结果
:-: ![](https://box.kancloud.cn/40fe3d2b11d5b8cbbb878e73ecdaa7ae_878x366.jpg)
图4 气象站输出结果(增加酷热指数)
**二、1、简单实现(二)之气象监测应用(使用Java内置的观察者模式来实现气象站获取数据并实时更新布告板的实例)**
修改后的气象站OO设计如下
:-: ![](https://box.kancloud.cn/a89fca3f62e2aba7a99e613fecb3349c_980x643.jpg)
图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);
}
}
~~~
输出结果
:-: ![](https://box.kancloud.cn/e65734d9f61a7403976cbc736fce64ed_655x361.jpg)
图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实例并组合到你自己的对象中。这设计违反了第二个设计原则:“多用组合,少用继承”。