例子
需求:已知有一个WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。WeatherData中有以下方法:getTemperature(), getHumidity(), gePressure(), measurementsChanged()。前三个方法用于获取最新的温度、湿度、气压,第四个方法会在气象测量更新时被调用,但是具体实现需要自己完成。
- 需要建立一个应用,有三个布告板,分别显示目前的状况、气象统计以及天气预报。
- 当WeatherObject对象获取最新的测量数据时,三种布告板必须实时更新。
- 公布一组api,使得其他开发人员可以写出自己的气象公布板。
第一种实现:
public void measurementsChanged() {
//获取最新的温度、湿度、气压
float temp = getTemperature();
float humidity = getHumidity();
float pressure = gePressure();
//更新所有布告板
currentConditionDisplay.update(temp, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastConditionDisplay.update(temp, humidity, pressure);
}
这种实现很容易理解,但是如果我们需要添加新的布告板了呢?是不是就必须修改代码。可以看到,它没有针对接口编程,布告板的更新都是针对具体实现,这会导致以后再增加或者删除布告板时必须修改程序。而其实每种布告板的方法更像是一个统一的接口,同样的方法名,同样的参数。
第二种实现:
现在我们来了解下观察者模式。举报纸订阅为例,报社每天发放报纸给订阅者,没有订阅该报纸的便不会收到报纸。用户可以随时订阅,也可以随时取消订阅。我们统一称类似于报社这样的主体为“主题”(Subject),订阅者为观察者(Observer)。
具体的主题实现主题的接口,具体的观察者实现观察者的接口。主题和观察者成“一对多”的关系。观察者通过主题的接口订阅或删除具体的主题,主题则调用观察者的接口里的update()。
通过这种方法,两个对象之间是松耦合的。主题只知道观察者实现了某个接口(也就是Observer接口),但是不知道观察者的具体类是什么。我们任何时候都可以增加新的观察者,而不会影响主题的代码。
//主题接口
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
//观察者接口
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
//用于布告板显示数据的接口
public interface DisplayElement {
public void display();
}
//具体的主题,此处即为WeatherData
public class WeatherData implements Subject {
//维护订阅该主题的观察者列表,保存的是接口
private List<Observer> 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 (Observer o : observers) {
observer.update(temp, humidity, pressure);
}
}
//当气象测量更新时调用
public void measurementsChanged() {
notifyObservers();
}
//气象测量更新
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
//具体的观察者,这里是目前状况公告板。其他公告板也类似。
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float pressure;
//订阅的主题,类型是接口
private Subject weatherData;
//在构造器中注册主题
public CurrentConditionDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
//当气象更新时,WeatherData会调用观察者的此方法
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
//数据展示
public void display() {
//展示当前的天气状况
...
}
}
如此,我们的气象系统就构建完成了。再来看看一开始的需求:
- 三个公告板。我们这里只实现了一个,但是都是类似,只要修改下display即可。
- 实时更新。当气象系统更新时,会遍历所有订阅它(主题)的布告板(观察者),然后调用他们的update()方法。
- 增加新的布告板。当有新的布告板时,我们的实现是非常简单的。新的布告板类只需要实现Observer和DisplayElement两个接口,然后分别重写它们的update()和display()。然后在声明时,只要在构造器中传入WeatherData的对象即可。
总结
设计模式:观察者模式
观察者模式定义了对象之间的一对多依赖,这样依赖,当一个对象改变状态时,它的所有依赖着都会收到通知并自动更新。
设计原则
- 找到程序中会变化的方面,然后将其和固定不变的方面相分离。 在观察者模式中,会改变的时主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。这个就叫提前规划。
- 针对接口编程,不针对实现编程。 主题和观察者都是用接口,观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。
- 多用组合,少用继承。 观察者模式利用“组合”将许多观察者组合进主题中。对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生的。
- 为交互对象之间的松耦合设计而努力。 以上。