观察者模式学习笔记
什么时候需要用观察者模式?
有的时候我们有这样的需求,当某一对象的状态更新了,需要告知其他的对象。同时,其他对象可以退出通知列表,也可以选择重新加入通知列表。这时候,我们就需要用到一种设计模式——观察者模式。
场景举例
上面的话太难以理解了,模拟一个应用场景来进行说明。
有一个气象站,能通过一些硬件设备获取到一些气象数据(温度、湿度、大气压)。同时有几个显示屏幕——实时信息、数据统计、气象预报等屏幕。当气象数据更新了之后,这些屏幕也要随着更新数据。另外,该应用还要扩展友好,能提供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 List<IObserver> observers;
public WeatherData() {
observers = new ArrayList<IObserver>();
}
@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