QWA`第二篇写观察者模式,也是书里的第二章,说到这个观察者模式,印象还是比较深刻的,因为去年软考软件设计师最后一道题,考的就是这个观察者模式。
概念
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
示例说明
需要做一个系统,这个系统包括三部分,气象站(获取实际气象数据的物理装置)、WeatherData 对象(追踪来自气象站的数据,并更新布告板)和布告板(显示目前天气状况给用户看)。
本例中,WeatherData 对象知道如何跟物理气象站联系,以取得更新的数据。WeatherData 对象还会更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报。
我们的目标是建立一个应用,利用 WeatherData 对象取得数据,然后更新三个布告板。
WeatherData 类包括的方法如下。
getTemplate()
getHumidity()
getPressure()
measuermentsChanged()
我们需要实现 measurementChanged() 方法
- 当新的测量数据备妥时,这个方法就会被调用
- 当 WeatherData 有新的测量数据,三块布告板必须马上更新
- 此系统必须可扩展,让其他开发人员建立定制的布告板,用户可添加或删除任何布告板,现有三个布告板,目前状况、气象统计以及天气预报布告板。
错误示范
首先来一个错误示范啊
public class WeatherData {
/**
* 目前状况布告板
*/
CurrentConditionDisplay currentConditionDisplay;
/**
* 气象统计布告板
*/
ForecastDisplay forecastDisplay;
/**
* 天气预报布告板
*/
StatisticsDisplay statisticsDisplay;
float temperature;
float humidity;
float pressure;
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
public float getTemperature() {
return temperature;
}
public void measurementsChanged() {
float temperature = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
currentConditionDisplay.update(temperature, humidity, pressure);
statisticsDisplay.update(temperature, humidity, pressure);
forecastDisplay.update(temperature, humidity, pressure);
}
}
这个类里面声明了几个实例,然后在 measurementsChanged() 方法里进行布告板的更新,当数据有了更新以后,会调用这个方法对布告板进行更新。
但这个方法弊病大了,它是针对具体实现编程,以后增加或删除布告板时必须修改程序。所以这个设计肯定是不可以用的,太耗费资源,并且需要反复修改代码。
修改
现在我们就可以聊到正题了,「观察者模式=主题+观察者」,那么什么是主题,什么是观察者?
主题,就是管理数据的对象,相当于监测中心,当数据有变化的时候,主题对象第一时间会通知观察者对象。
观察者对象注册到主题,当数据有变化的时候,会接收到主题的数据更新。就类似于报社和读者之间的关系一样,当有新的新闻需要发表的时候,报社印刷报纸,派送到读者手中。这是一对多的关系,当一个对象改变时,其他的依赖者都会收到通知。
设计原则:为了交互对象之间的松耦合设计而努力。
对于观察者对象,主题只知道观察者实现了某个接口(Observer接口),主题不需要知道观察者的具体类是谁,做了些什么或其他任何细节。而且观察者可以随意增减,而并不会受到影响。有新的观察者出现时,主题代码不用修改,需要做的就是在新类中实现此观察者接口,然后注册为观察者。
观察者模式就是为了让主题和观察者松耦合,他们可以进行交互,但不清楚彼此的细节。改变他们两者之间的任何一方,都不会影响另一方,因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以*地改变他们。
接下来就看看具体的代码吧。
代码实现示例
首先是三个接口,分别是 DisplayElement、Observer 和 Subject 三个接口,顾名思义,第一个接口用于布告板展示,第二个接口是观察者接口,用以进行数据的更新,第三个接口是主题接口,用来注册观察者。
public interface DisplayElement {
public void display();
}
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
public interface Subject {
public void registerObserver(Observer observer);
}
接下来是我们之前提到过的,WeatherData 类,这个类由于需要通知观察者进行数据更新,所以它是主题类,需要实现 Subject 接口,包含一个观察者的集合。
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 observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
int i = observers.indexOf(observer);
if (i >= 0) {
observers.remove(observer);
}
}
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();
}
}
observers 是观察者的集合,在构建 Weather 对象时同时实例化一个空的集合,用以做注册观察者对象的容器,其余三个属性是布告板需要的属性。
registerObserver(Observer observer) 方法是注册观察者的方法,同理 removeObserver(Observer observer) 方法是删除观察者的方法。
notifyObservers() 方法就是遍历集合通知观察者数据有更新,measurementsChanged() 方法就是前文提到的更新数据的方法。
setMeasurements(float temperature, float humidity, float pressure)(float temperature, float humidity, float pressure) 方法是设置那三个属性的方法,同时调用measurementsChanged() 方法通知布告板进行数据更新。
接下来就是布告板 CurrentConditionsDisplay 类。它实现了观察者接口和实现接口,包含一个主题对象,在初始化实例的时候需要传入主题对象进行注册,实现两个接口中的方法。
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);
}
@Override
public void display() {
System.out.println("Current conditions: " + temperature +
"F degrees and " + humidity + "% humidity");
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
}
最后是测试类。
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(80,65, 30.4f);
}
}
创建一个主题对象,创建一个布告板对象,通过主题对象的进行更新数据,会在控制台打印出布告板要展示的数据。
这是一个明显的一对多结构,在一的一方存有集合,多的一方存有一的对象。当需要进行数据更新的时候,调用方法进行一连串的行动,这样将二者进行松耦合以后,想要添加新的布告板只需要建立新类实现 Observer 接口,然后注册到主题对象即可。
明天继续下个设计模式。