参考《Head First设计模式》中的观察者模式完成。
根据气象站实时输出的湿度、温度和气压值制作三块布告板。第一块布告板实时显示当前的温度、湿度和气压;第二块布告板显示当日的平均温度、最低温度以及最高温度;第三块布告板根据天气显示预报信息。
气象站提供了WeatherData类来获得实时测量的温度、湿度和气压值。
气象站提供的接口如上如所示,三个getter方法用于获取温度、湿度和气压;每当气象测量值变化,就会调用measurementsChanged()方法,实现measurementsChanged()就是我们的工作。
分析:
考虑到需求中布告板显示的内容可能会发生变化,为了方便日后修改程序,希望每次只需要添加一块新的布告板,而其他程序不需要变化,努力做到交互对象之间松耦合。这个需求中,天气测量值一旦变化,三个布告板要及时获得改变并展示,完全符合观察者模式。
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,他的所有依赖者都会收到通知并自动更新。
观察者模式中有主题和观察者两个对象,以报纸的订阅为例简述观察者模式。
只要报社还在运营,就一直有人向他们订阅或取消订阅报纸。
把订阅报纸类比为观察者模式,出版者就是“主题”,订阅者就是“观察者”。在该需求中,气象站就是一个“主题”,而每块布告板就是一个“观察者”。每个布告板(观察者)向气象站(主题)注册,就可以在测量值变化时获得消息;取消某布告板类比为“观察者”向“主题”注销;新增一块布告板类比为新增一个气象“主题”的“观察者”。
面向对象的设计原则:针对接口编程,不针对实现编程。
根据上述原则,把主题和观察者分别抽象为Subject和Observer接口。并添加一个DisplayElement接口用于展示。
Observer接口中只有一个update()方法,用于当主题变化时执行。
Subject接口中有使得观察者订阅的方法registerObserver();当观察者不想接收信息时的取消订阅方法removeObserver();以及当主题数据发生改变时通知订阅该主题的所有观察者的notifyObserver()方法。实际的主题,例如本需求中的WeatherData需要实现该接口,内部维护一个观察者数组。registerObserver()方法中只需要把观察者加入自身的观察者数组;removeObserver()方法中把要取消订阅的观察者移除;notifyObserver()则是遍历当前的观察者数组,依次调用每个观察者的update()即可。
/**
* Created by Janet on 2017/11/6.
* 观察者模式中主题的接口
*/
public interface Subject {
public void registerObserver(Observer o);//观察者o订阅主题
public void removeObserver(Observer o);//观察者o取消订阅主题
public void notifyObserver();//主题通知观察者
}
/**
* Created by Janet on 2017/11/6.
* 观察者模式中观察者的接口
*/
public interface Observer {
public void update(float temp,float humidity,float pressure);//主题传来通知
}
/**
* Created by Janet on 2017/11/9.
* 展示结果的接口
*/
public interface DisplayElement {
public void display();
}
import java.util.ArrayList;
/**
* Created by Janet on 2017/11/6.
* 气象预报的主题
*/
public class WeatherData implements Subject {
private ArrayList observers;//观察者列表
private float temperature;//温度
private float humidity;//湿度
private float pressure;//气压
//每次设置温湿度和气压,主题都会发生改变
public void setTemperature(float temperature,float humidity,float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public WeatherData(){
observers = new ArrayList();
}
//主题发生改变时执行的方法
public void measurementsChanged(){
notifyObserver();
}
//观察者o订阅主题
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
//观察者o取消订阅主题
@Override
public void removeObserver(Observer o) {
int index = observers.indexOf(o);
if( index >= 0 ){
observers.remove(o);
}
}
//主题通知所有订阅者
@Override
public void notifyObserver() {
for(int i = 0;i
/**
* Created by Janet on 2017/11/6.
* 第一块展示温湿度的布告板
*/
public class CurrentConditionDisplay implements Observer,DisplayElement {
private Subject weatherData;
private float temperature;
private float humidity;
//一创建就向主题注册
public CurrentConditionDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Current condition : "+temperature+"F degrees and "+humidity+"% humidity");
}
//主题变化时执行的函数
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
/**
* Created by Janet on 2017/11/6.
* 第二块展示最小,平均,最大温度的布告板
*/
public class StatisticsDisplay implements Observer,DisplayElement {
private float minTemperature = Float.MAX_VALUE;//最小温度
private float avgTemperature;//平均温度
private float maxTemperature = Float.MIN_VALUE;//最大温度
private int num = 0;//用于计算平均温度
private Subject weatherData;
//创建观察者时要向主题注册
public StatisticsDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Avg/Max/Min temperature = "+avgTemperature+"/"+maxTemperature+"/"+minTemperature);
}
//主题变化时执行的函数
@Override
public void update(float temp, float humidity, float pressure) {
if( minTemperature > temp ){
minTemperature = temp;
}
if( maxTemperature < temp ){
maxTemperature = temp;
}
avgTemperature = (avgTemperature * num + temp)/(num+1);
num++;
display();
}
}
/**
* Created by Janet on 2017/11/6.
*/
public class WeatherStation {
public static void main(String[] args){
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay o1 = new CurrentConditionDisplay(weatherData);//第一块布告板
StatisticsDisplay o2 = new StatisticsDisplay(weatherData);
weatherData.setTemperature(19,20,1000);
weatherData.setTemperature(13,15,1500);
weatherData.setTemperature(0,22,1000);
}
}
java.util包中的Observable是主题的超类(注意,java内置的主题是类不是接口),Observer是观察者的接口。
实际主题需要继承超类Observeable,已经写好了观察者订阅,取消订阅以及通知的方法。其中setChanged()方法用于当主题数据修改时,用来标记状态已经改变。WeatherData内部的notifyObservers()方法会首先判断标志位是否更改,再通知各观察者。
观察者获取主题变化的数据实际上有“推”和“拉”两种方式。“推”表示主题数据发生变化时,主动把变化的数据推送给订阅的观察者们;“拉”表示当观察者需要时主动向主题索取数据。上文中我们只是自己实现了主题“推数据”的方法。Java内置的观察者模式支持“推”和“拉”两种获取数据的模式。
注意,使用Java内置主题时,notifyObservers()有两种重载方法:
notifyObservers()和notifyObservers(Object arg);notifyObservers(Object arg)可以传送指定数据给观察者。notifyObservers()用于拉数据的模式,notifyObservers(Object arg)用于推的模式。
import java.util.Observable;
/**
* Created by Janet on 2017/11/9.
* 使用java内置类实现主题
*/
public class WeatherData extends Observable{//继承Observable,内部实现了主题的创建观察者列表等方法
private float temperature;
private float humidity;
private float pressure;
public WeatherData(){}//此处无需自行创建观察者列表,超类已经创建
public void setMeasurements(float temperature,float humidity,float pressure){
this.pressure = pressure;
this.humidity = humidity;
this.temperature = temperature;
measurementsChanged();//数据改变后调用该方法
}
private void measurementsChanged() {
setChanged();//设置改变标志位
notifyObservers();//没有参数传入,说明是观察者向主题索取数据
}
public float getTemperature(){
return temperature;
}
public float getHumidity(){
return humidity;
}
public float getPressure(){
return pressure;
}
}
注意,Java内置的观察者获取主题动态有“推”和“拉”两种模式。推即为主题
import java.util.Observable;
import java.util.Observer;
/**
* Created by Janet on 2017/11/9.
* 使用java内置类实现观察者
*/
public class CurrentConditionsDisplay implements Observer,DisplayElement {
private Observable observable;//观察者内部记录主题
private float temperature;
private float humidity;
//构造函数中把观察者加入到主题中
public CurrentConditionsDisplay(Observable observable){
this.observable = observable;
observable.addObserver(this);//主题把观察者加入到列表
}
@Override
public void display() {
System.out.println("Current conditions: "+temperature+" F degrees and "+humidity+"% humidity");
}
//不同的观察者索取的数据由update方法展示
@Override
public void update(Observable o, Object arg) {
if( o instanceof WeatherData ){
WeatherData weatherData = (WeatherData) o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
}
import java.util.Observable;
import java.util.Observer;
/**
* Created by Janet on 2017/11/9.
*/
public class StatisticsDisplay implements Observer,DisplayElement {
private Observable observable;
private float minTemperature = Float.MAX_VALUE;//最小温度
private float avgTemperature;//平均温度
private float maxTemperature = Float.MIN_VALUE;//最大温度
private int num = 0;//用于计算平均温度
public StatisticsDisplay(Observable observable){
this.observable = observable;
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("Avg/Max/Min temperature = "+avgTemperature+"/"+maxTemperature+"/"+minTemperature);
}
@Override
public void update(Observable o, Object arg) {
if( o instanceof WeatherData ){
WeatherData weatherData = (WeatherData) o;
float temp = weatherData.getTemperature();
if( minTemperature > temp ){
minTemperature = temp;
}
if( maxTemperature < temp ){
maxTemperature = temp;
}
avgTemperature = (avgTemperature * num + temp)/(num+1);
num++;
display();
}
}
}
import java.util.Observable;
import java.util.Observer;
/**
* Created by Janet on 2017/11/9.
*/
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() {
if( this.currentPressure < this.lastPressure ){
System.out.println("气压变小");
}else if( this.currentPressure > this.lastPressure ){
System.out.println("气压变大");
}else{
System.out.println("气压不变");
}
}
@Override
public void update(Observable o, Object arg) {
if( o instanceof WeatherData ){
WeatherData weatherData = (WeatherData) o;
this.lastPressure = currentPressure;
this.currentPressure = weatherData.getPressure();
display();
}
}
}
/**
* Created by Janet on 2017/11/9.
*/
public class WeatherStation {
public static void main(String[] args){
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setMeasurements(19,20,1000);
weatherData.setMeasurements(13,15,1500);
weatherData.setMeasurements(0,22,1000);
}
}