设计模式学习--观察者模式(Oberser Pattern)
2013年5月18日 天气:热!
下午15:28 设计模式学习中
学习者:小巫
什么是观察者模式?
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
怎么设计一个观察者模式的程序?
确定两个对象:1. 主题
2.观察者
确定这两个对象之间的关系:
主题对象管理某些数据,一旦数据发生改变,会主动向观察者进行通知,然而观察者不必向主题进行索取。
主题并不知道具体的观察者是谁,这是它们之间的关系。
以上涉及到的设计原则:
为了交互对象之间的松耦合设计而努力
具体实例:气象站的实现
1. 定义一个主题接口Subject
package observerPattern;
/**
* 主题接口
* @author wwj
*
*/
public interface Subject {
public void registerObserver(Observer o); //这两个方法都需要一个观察者作为变量,该观察者是用那个来注册和删除的
public void removeObserver(Observer o);
public void notifyObserver(); //当主题状态发生改变时,这个方法会被调用,以通知所有的观察者
}
2. 定义一个观察者接口Observer
package observerPattern;
/**
* 观察者接口
* @author wwj
*
*/
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
3. 定义一般气象布告板接口DisplayElement
package observerPattern;
/**
* 公告板接口
* @author wwj
*
*/
public interface DisplayElement {
public void display();
}
4. 定义主题类:WeatherData实现接口
package observerPattern;
import java.util.ArrayList;
/**
* WeatherData实现了Subject接口
* @author wwj
*
*/
public class WeatherData implements Subject {
private ArrayList observers; //用于记录观察者
private float temperature; //温度
private float humidity; //湿度
private float pressure; //压力
public WeatherData() {
observers = new ArrayList();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i >= 0) {
observers.remove(i);
}
}
@Override
public void notifyObserver() {
for(int i = 0; i < observers.size(); i++) {
Observer observer = (Observer)observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObserver();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
5. 定义四个布告板类实现观察者接口和布告板接口
package observerPattern;
/**
* 观察者类实现观察者接口和显示板接口
* @author wwj
*
*/
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weathderData;
public CurrentConditionDisplay(Subject weathderData) {
this.weathderData = weathderData;
weathderData.registerObserver(this); //注册
}
@Override
public void display() {
System.out.println("Current coditions: " + temperature + "F degress and " + humidity + "% humidity");
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
package observerPattern;
/**
* 天气统计布告板
* @author wwj
*
*/
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);
}
@Override
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
}
@Override
public void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if(temp > maxTemp) {
maxTemp = temp;
}
if(temp < minTemp) {
minTemp = temp;
}
display();
}
}
package observerPattern;
/**
* 天气预报布告板
* @author wwj
*
*/
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);
}
@Override
public void display() {
System.out.println("Forcast:");
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");
}
}
@Override
public void update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
}
package observerPattern;
/**
* 酷热指数布告板
*
* @author wwj
* 注:那个计算酷热指数的公式不必深究
*/
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);
}
}
6. 来吧,开始测试
package observerPattern;
/**
* 测试类
* @author wwj
*
*/
public class WeatherStation {
public static void main(String[] args) {
//建立一个WeatherData对象
WeatherData weatherData = new WeatherData();
//第一个布告板
CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(
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);
}
}
7. 测试结果:
Current coditions: 80.0F degress and 65.0% humidity
Avg/Max/Min temperature = 80.0/80.0/80.0
Forcast:
Improving weather on the way!
Heat index is 82.95535
Current coditions: 82.0F degress and 70.0% humidity
Avg/Max/Min temperature = 81.0/82.0/80.0
Forcast:
Watch out for cooler, rainy weather
Heat index is 86.90124
Current coditions: 78.0F degress and 90.0% humidity
Avg/Max/Min temperature = 80.0/82.0/78.0
Forcast:
more of the same
Heat index is 83.64967
以上的观察者模式实现是通过主题以“推”的方式通知观察者们,观察者可以在一次通知中一口气得到所有东西。
因为观察者与主题发生了争吵,观察者有自己的想法,希望能“拉”走主题的状态,然而Java内置的Observer模式就支持这样,下面来看看吧。
1. 继承Observable类的WeatherData(不再需要自定义接口了,但这样真的好吗?)
package weatherObservable;
import java.util.Observable;
/**
* 使用Java内置的观察者模式
* @author wwj
*
*/
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
/**
* 我们的构造器不再需要为了记住观察者们而建立数据结构了
*/
public WeatherData(){}
public void measurementsChanged() {
setChanged(); //Observable类方法
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;
}
}
2. 4个布告板中的代码稍微发生了点变化
package weatherObservable;
import java.util.Observable;
import java.util.Observer;
/**
* 实现Java内置的观察者接口,布告板不变
* @author wwj
*
*/
public class CurrentConditionDisplay implements Observer, DisplayElement{
Observable observable;
private float temperature;
private float humidity;
public CurrentConditionDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this); //登记为观察者
}
@Override
public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
}
/**
* 在这个方法当中,先确定可观察者属于WeatherData类型,然后利用getter方法获取温度和温度测量值,最后调用display();
*/
@Override
public void update(Observable obs, Object arg) {
if(obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData) obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
}
package weatherObservable;
import java.util.Observable;
import java.util.Observer;
/**
* 天气预报布告板
* @author wwj
*
*/
public class ForecastDisplay implements Observer, DisplayElement {
private Observable observable;
private float currentPressure = 29.92f; //当前气压
private float lastPressure; //以往气压
public ForecastDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("Forcast:");
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");
}
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
lastPressure = currentPressure;
currentPressure = weatherData.getPressure();
display();
}
}
}
package weatherObservable;
import java.util.Observable;
import java.util.Observer;
/**
* 天气统计布告板
* @author wwj
*
*/
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;; //最大温度
private float minTemp = 200; //最小温度
private float tempSum = 0.0f; //统计温度和
private int numReadings; //统计温度次数
private Observable observable;
public StatisticsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
}
@Override
public void update(Observable obs, Object arg) {
if(obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData)obs;
float temp = weatherData.getTemperature();
tempSum += temp;
numReadings++;
if (temp > maxTemp) {
maxTemp = temp;
}
if (temp < minTemp) {
minTemp = temp;
}
display();
}
}
}
package weatherObservable;
import java.util.Observable;
import java.util.Observer;
/**
* 酷热指数布告板
*
* @author wwj
* 注:那个计算酷热指数的公式不必深究
*/
public class HeatIndexDisplay implements Observer, DisplayElement {
float heatIndex = 0.0f;
private WeatherData weatherData;
private Observable observable;
public HeatIndexDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
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);
}
@Override
public void update(Observable obs, Object arg) {
if(obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData)observable;
float t = weatherData.getTemperature();
float rh = weatherData.getHumidity();
heatIndex = computeHeatIndex(t, rh);
}
display();
}
}
3. 测试类不变
package weatherObservable;
/**
* 测试类
* @author wwj
*
*/
public class WeatherStation {
public static void main(String[] args) {
//建立一个WeatherData对象
WeatherData weatherData = new WeatherData();
//第一个布告板
CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(
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. 但测试结果发生了变化:观察者被通知的次序发生了变化
Heat index is 82.95535
Forcast:
Improving weather on the way!
Avg/Max/Min temperature = 80.0/80.0/80.0
Current conditions: 80.0F degrees and 65.0% humidity
Heat index is 86.90124
Forcast:
Watch out for cooler, rainy weather
Avg/Max/Min temperature = 81.0/82.0/80.0
Current conditions: 82.0F degrees and 70.0% humidity
Heat index is 83.64967
Forcast:
more of the same
Avg/Max/Min temperature = 80.0/82.0/78.0
Current conditions: 78.0F degrees and 90.0% humidity
以上的实现被认为不是那么“正确”的,为什么呢?
有以下原因:
1. Observable是一个“类”,而不是一个接口,也没有实现一个接口,限制了它的使用和复用。
2. Observable将关键的方法保护起来了,违反了 “多用组合,少用继承”的设计原则。
当然在JDK中不只有这个地方用到了观察者模式,比如以下几个地方也用到了:
1. Swing
2. JavaBeans
3. RMI
好啦,第二个设计模式:观察者模式被小巫收入囊中,这实在太有趣了。
下一个模式将会是:装饰者模式。