背景:建立一个应用,利用后台的核心对象WeatherData获取气象站的数据,并更新应用的的三个布告板:目前状况、气象统计和天气预报。一旦后台的WeatherData数据有更新,布告板应该马上更新,或者知道数据更新了,从而执行刷新操作。系统应该可扩展,让用户可以随心所欲的添加删除布告板。
/**
* 简单的应用后台
*/
public class WeatherData {
/**
* 目前天气布告板
*/
private CurrentConditionsDisplay currentConditionsDisplay;
/**
* 天气统计布告板
*/
private StatisticsDisplay statisticsDisplay;
/**
* 天气预告布告板
*/
private ForecastDisplay forecastDisplay;
/**
* (假设定时任务每分钟都会调取该方法)获取气象数据,并判断是否需要更新布告板
*/
public void handleData() {
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
if (!"温度、湿度、气压".equals("不等于当前的布告板数据,则更新布告板")) {
measurementsChanged(temp, humidity, pressure);
}
}
public void measurementsChanged(float temp, float humidity, float pressure) {
//调用目前天气布告板对象的更新方法布告板
currentConditionsDisplay.update(temp, humidity, pressure);
//调用天气统计布告板对象的更新方法布告板
statisticsDisplay.update(temp, humidity, pressure);
//调用天气预告布告板对象的更新方法布告板
forecastDisplay.update(temp, humidity, pressure);
}
public float getTemperature() {
System.out.println("获取温度数据");
return new Random().nextFloat();
}
public float getHumidity() {
System.out.println("获取湿度数据");
return new Random().nextFloat();
}
public float getPressure() {
System.out.println("获取气压数据");
return new Random().nextFloat();
}
}
1.如果上述的CurrentConditionsDisplay等布告板对象为具体类,而非接口,那么违背了针对接口编程,而非具体实现编程的原
则,会导致每次修改布告板都需要重新打开具体类进行修改代码
2.对每个新的布告板都需要修改核心代码(修改WeatherData的代码),也无法满足定制需求(动态的增加或删除布告板)
3.尚未封装改变的部分(update方法就是改变的部分,根据需求的不一样,虽然获取一样的数据,处理方式却不一样,所以可定制
成一个接口)
此题主要是讲观察者模式,那么解决此问题的办法自然是观察者模式,先看看什么是观察者模式。
在这里WeatherData,从探测器处获取数据,从而持有数据,然后布告板有N个,当数据变化时每个布告板都从WeatherData处获取一样的数据来更新各自的布告板,那么可以理解:N个布告板从WeatherData处订阅数据,当WeatherData的数据出现变化时,便广播给布告板,它们一对多的关系,此处将WeatherData称之为‘主题’,布告板称为‘观察者’,‘主题’+‘观察者’=观察者模式
实现观察者最常见的是包含Subject与Observer接口的类设计,关系看以下类图
Subject接口定义主题的订阅、取消订阅等方法。Subject的实现类即真正的主题(仅一个实现)、数据的持有者,使用ArrayList集合来记录实现了Observer接口的订阅者(多个实现)。观察者持有Subject的引用,方便注册订阅、取消订阅。
至此,我们便可以简单搭建起观察者模式的气象应用了,代码如下:
//主题接口
public interface Subject {
//消费者注册接口,进行数据订阅
public void registerObserver(Observer observer);
//消费者移除接口,取消数据订阅
public void removeObserver(Observer observer);
//数据广播接口,当数据出现变更时,将更新后的数据传输给消费者
public void notifyObservers();
}
//天气主题--实现主题接口
public class WeatherData implements Subject {
//成员变量集合容器,用来承装消费者接口的引用,消费者进行数据订阅就添加到容器中去,取消订阅就从容器中移除
private ArrayList observers;
private double temp;
private double humidity;
private double pressure;
public WeatherData() {
//构造器中实例化容器
this.observers = new ArrayList<>();
}
@Override
/**
*将消费者的引用传递给此方法添加到订阅者容器中进行数据订阅
**/
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
/**
*将消费者的引用传递,看消费者是否订阅了数据,如订阅 了,取消订阅
**/
public void removeObserver(Observer observer) {
int i = observers.indexOf(observer);
if (i>=0){
observers.remove(i);
}
}
@Override
public void notifyObservers() {
//遍历集合容器,然后将数据传递各自订阅者的处理方法进行数据处理
observers.forEach(e->e.update(temp,humidity,pressure));
}
/**
*设置最新获取到的数据
**/
public void setMeasurements(double temp,double humidity,double pressure){
this.temp=temp;
this.humidity=humidity;
this.pressure=pressure;
//同时将最新获取到的数据广播给每一个订阅了数据的消费者
measurementsChanged();
}
public void measurementsChanged(){
//调用广播方法
notifyObservers();
}
//其他方法
}
===================================== 观察者=====================================
//观察者接口
public interface Observer {
//接收广播数据
public void update(double temp,double humidity,double pressure);
}
//展示接口(封装变化,具体的数据处理接口)
public interface DisplayElement {
public void display();
}
//观察者(实现了Observer接口便有资格成为观察者,调用registerObserver方法后注册成功即成观察者)
public class CurrentConditionDisplay implements Observer,DisplayElement {
private double temp;
private double humidity;
//持有订阅主题的引用,以便订阅
private Subject weatherData;
public CurrentConditionDisplay(Subject weatherData) {
//构造器中,传入主题的引用,进行成员变量的初始化
this.weatherData = weatherData;
//调用主题的注册方法,进行数据订阅
weatherData.registerObserver(this);
}
@Override
//数据处理的具体实现
public void display() {
System.out.println("Current condition:"+temp+"F degrees and "+humidity+"% humidity");
}
@Override
public void update(double temp, double humidity, double pressure) {
this.temp=temp;
this.humidity=humidity;
//获取最新数据后,封装到消费者中,然后调用数据处理方法进行数据处理
display();
}
}
另外两个布告板就不再贴代码了,一样的思路,输出代码也不再展示,看懂了测试便很简单了。
观察者模式--在对象之间定义一对多得依赖,如此,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。
1、CurrentConditionDisplay实现了DisplayElement 接口,如果有其他的具体类实现DisplayElement 接口,同时功能是一样的,那
岂不是会造成代码的冗余,无法复用的问题?如何解决此问题,可以看上一篇策略模式来解决。
策略模式跳板
2、通过代码可以看出,主题广播数据的方式是通过循环集合中的消费者来调取update将数据传输至消费者中的,在观察者模式中
称之为推送。还有另外一种在文中没有体现的:不管主题数据是否有变化,想要得到最新数据,便主动的去拉取数据,另一种消费
者获取数据的方式。JDK内置有观察者模式,支持推、拉两种数据获取方式都支持,这里不再赘述,如有论述,将在下篇文章中进
行书写。