## 什么时候需要用观察者模式?
有的时候我们有这样的需求,当某一对象的状态更新了,需要告知其他的对象。同时,其他对象可以退出通知列表,也可以选择重新加入通知列表。这时候,我们就需要用到一种设计模式——观察者模式。
## 场景举例
上面的话太难以理解了,模拟一个应用场景来进行说明。
有一个气象站,能通过一些硬件设备获取到一些气象数据(温度、湿度、大气压)。同时有几个显示屏幕——实时信息、数据统计、气象预报等屏幕。当气象数据更新了之后,这些屏幕也要随着更新数据。另外,该应用还要扩展友好,能提供API接口,能添加或者删除屏幕设备。场景分析
先分析一下场景,能发现几个对象(object)。气象数据(WeatherData
),实时信息屏幕(CurrentConditionDisplay
),数据统计屏幕(statisticsDisplay
),气象预报屏幕(ForcastDisplay
)。
/** * 气象数据 */class WeatherData { getTemperature(); getHumidity(); getPressure(); measurementsChanged();}
/** * 实时信息屏幕 */class CurrentConditionDisplay() { display();}
/** * 数据统计屏幕 */class statisticsDisplay() { display();}
/** * 气象预报屏幕 */class ForcastDisplay() { display();}
先完善一下 WeatherData
的代码
public class WeatherData { public void measurementsChanged() { float temp = getTemperature(); float humidity = getHumidity(); float pressure = getPressure(); currentConditionsDisplay.update(temp, humidity, pressure); statisticsDisplay.update(temp, humidity, pressure); forecastDisplay.update(temp, humidity, pressure); } }
发现不足
上面WeatherData
的这段代码好像完成了数据更新的功能。但是有几个不足的地方。
- 针对实现编程,而不是针对接口编程
- 当有新的显示屏幕,我们需要改动
WeatherData
中的measurementsChanged()
方法 - 不能在程序运行是时添加或移除显示屏对象
- 所有的显示屏对象虽然都有同一个
update()
方法,却没有实现一个通用接口
如何改进?
- 使用接口,这样就能在程序运行时增加或移除显示屏幕
- 所有的显示屏幕实现一个通用显示接口,实现该接口的
update()
方法。
改进的具体实现,先放着。先回到观察者模式上来。
重新审视观察者模式
举个更接近观察者模式定义的例子:
就像订阅报纸一样,你可以订阅报纸,这样当有新的报纸到了,就有新的报纸看。如果你取消订阅了,那下一期的报纸就无法看到了。
上面这句话有两种行为发布和订阅。观察者模式其实就是这样的
Publisher + Observer = Observer Pattern
翻译成中文就是:发布者 + 观察者 = 观察者模式
说了这么多,是时候来一个官方定义了。
The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.
翻译一下就是:
观察者模式 定义了对象间一对多的关系。所以当一个对象的状态变化了,它的依赖对象就会被唤醒和自动更新状态。
注意关键点:
- 一对多
- 状态变化时,依赖对象随之更新
如何实现这个程序?
解耦
设计原则:Strive for loosely coupled designs between objects that interact.
翻译过来就是:为它们之间有交互的对象解耦而努力
代码关系如下:
/** * Publisher接口 */interface Subject { registerObserver(); removeObserver(); notifyObserver();}
/** * 气象数据实现Publisher接口,作为一个Subject */public class WeatherData implements Subject { private float temp; private float humidity; private float pressure; private Listobservers; public WeatherData() { observers = new ArrayList (); } @Override public void registerObserver(IObserver observer) { observers.add(observer); } @Override public void removeObserver(IObserver observer) { int index = observers.indexOf(observer); if (index > 0) { observers.remove(index); } } @Override public void notifyObserver() { for (IObserver ob : observers) { ob.update(getTemp(), getHumidity(), getPressure()); } } public void measurementsChanged() { notifyObserver(); } public void setMeasurements(float temp, float humidity, float pressure) { this.temp = temp; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } public float getTemp() { return temp; } public void setTemp(float temp) { this.temp = temp; } public float getHumidity() { return humidity; } public void setHumidity(float humidity) { this.humidity = humidity; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; }}
/** * Observer接口 */public interface Observer { public void update(float temp, float humidity, float pressure);}
因为所有的显示屏都有一个update()
方法,因此规定一个公共接口
/** * 屏幕显示接口 */public interface IDisplayElement { public void display();}
接着实现Observer即显示屏幕
/** * 实时信息显示屏 */public class ConditionsDisplay implements Observer, DisplayElement { private float temp; private float humidity; private ISubject weatherData; public ConditionsDisplay(ISubject weatherData) { this.weatherData = weatherData; this.weatherData.registerObserver(this); } @Override public void update(float temp, float humidity, float pressure) { this.temp = temp; this.humidity = humidity; display(); } @Override public void display() { System.out.println("Current conditions: " + temp + "F degrees and " + humidity + "% humidity"); }}
其他的几个显示屏类也是类似的,就不写了。
接着写一个测试类App
来进行验证
public class App { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); ConditionsDisplay conditionsDisplay = new ConditionsDisplay(weatherData); weatherData.setMeasurements(10.3f, 45.12456f, 123.456f); } }
打印结果:
output:-----------------------------------------------------------Current conditions: 10.3F degrees and 45.12456% humidity
总结
- 观察者模式是 one-to-many 的关系。一个对象变化,其依赖的对象也随之更新
- 为了扩展性,应该是面向接口编程而不是面向实现编程
- Publisher + Observer = Observer Pattern