一、了解观察者模式
1.1 什么是观察者模式
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象状态改变时,它的所有依赖者都会收到通知并自动更新。
典型的问题比如报社,只要你是他们的订户,他们每次有新报纸出版时,就会向你这送来,当你不想要看报纸时,取消订阅,他们就不会再给你送报纸。
1.2 观察者模式组成结构
- 抽象主题 (Subject):抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- 具体主题 (ConcreteSubject):该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- 抽象观察者 (Observer):是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- 具体观察者 (ConcrereObserver):实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
1.3 观察者模式 UML 图解
二、观察者模式具体应用
2.1 问题描述
气象观测站系统:该系统中包含三部分,分别是气象站 (获取实际气象数据的物理装置)、WeatherData
对象 (追踪气象站的数据,并更新布告板) 和布告板 (显示天气状况给用户看,布告板共有两个,分别显示当前的温度以及对天气进行预告)。
2.2 问题分析
我们想要使用观察者模式去解决这个问题,首先要分析出什么是主题,什么是观察者,问题的关键是找出一对多依赖关系。这里 WeatherData
类正如所说的“一”,而“多”是用于显示天气情况的布告板。
WeatherData
是有状态的对象,它包括了温度、湿度和气压,而这些值都会变化,当这些值改变时,必须通知布告板,好让它们显示最新的数据。所以把 WeatherData
类作为主题,布告板作为观察者。
2.3 问题分析设计图
2.4 代码实现
PS:代码模块较多,建议将这些代码拷下来运行一遍。
抽象主题接口 Subject
package com.jas.observer;
public interface Subject {
/**
* 注册观察者
*
* @param observer 观察者对象
*/
void registObserver(Observer observer);
/**
* 移除观察者
*
* @param observer 观察者对象
*/
void removeObserver(Observer observer);
/**
* 当主题状态改变时,这个方法会被调用,通知所有的观察者
*/
void notifyObservers();
}
抽象观察者接口 Observer
package com.jas.observer;
public interface Observer {
/**
* 当气象观测值改变时,主题会把这些状态值作为参数,传送给观察者
*
* @param temp 温度
* @param humidity 湿度
* @param pressure 压力
*/
void update(float temp, float humidity, float pressure);
}
布告信息接口 DisplayElement
package com.jas.observer;
public interface DisplayElement {
void display();
}
具体主题类 WeatherData
package com.jas.observer;
import java.util.ArrayList;
import java.util.List;
public class WeatherData implements Subject {
private float temperature;
private float humidity;
private float pressure;
private List<Observer> list = new ArrayList(); //使用集合保存所有的观察者对象
@Override
public void registObserver(Observer observer) {
list.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int i = list.indexOf(observer);
if(i >= 0 && i < list.size()){
list.remove(i);
}
}
@Override
public void notifyObservers() {
for (int i = 0; i < list.size(); i++) { //遍历集合中所有观察者对象
Observer observer = list.get(i);
observer.update(temperature,humidity,pressure); //调用观察者的 update() 方法
}
}
/**
* 当气象站的数据得到更新后,通知观察者,调用 notifyObservers() 方法
*/
public void measurementsChanged(){
notifyObservers();
}
/**
* 当气象站数据改变后,设置新的数据值,并调用 measurementsChanged() 方法
*
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
具体观察者对象,当前天气信息类 CurrentConditionsDisplay
package com.jas.observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float pressure;
private Subject weatherData;
/**
* 通过构造函数将当前观察者注册给具体主题对象
*
* @param weatherData 主题对象
*/
public CurrentConditionsDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registObserver(this);
}
/**
* 布告板信息展示
*/
@Override
public void display() {
System.out.println("Current conditions list : " + "温度 = " +
temperature + ", 湿度 = " + humidity + ", 气压 = " + pressure);
}
/**
* 更新信息
*
* @param temp 温度
* @param humidity 湿度
* @param pressure 压力
*/
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
具体观察者对象,预测天气信息类 ForecastDisplay
(简单将数据减一)
package com.jas.observer;
public class ForecastDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float pressure;
private Subject weatherData;
/**
* 通过构造函数将当前观察者注册给主题对象
*
* @param weatherData 主题对象
*/
public ForecastDisplay(WeatherData weatherData){
this.weatherData = weatherData;
weatherData.registObserver(this);
}
@Override
public void display() {
System.out.println("Forecast conditions list : " + "温度 = " +
(temperature - 1.0) + ", 湿度 = " + (humidity - 1.0) + ", 气压 = " + (pressure - 1.0));
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
气象站类 WeatherStation
package com.jas.observer;
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
//当具体主题对象数据发生变化,所有依赖者 (观察者) 都会实现自动数据更新
weatherData.setMeasurements(18,65,30);
}
}
/**
* 输出
* Current conditions list : 温度 = 18.0, 湿度 = 65.0, 气压 = 30.0
* Forecast conditions list : 温度 = 17.0, 湿度 = 64.0, 气压 = 29.0
*/
2.5 自定义观察者模式总结
观察者模式可以轻松实现松耦合,因为主题并不需要知道观察者的具体类是谁,做了些什么,并且我们可以在任何时候新增观察者。由于一个主题可能对应多个观察者,所以当某一个观察者出现问题时,可能导致其他的观察者也不能正常工作。因此在一定程度上,存在着效率问题。
三、Java 内置观察者模式
3.1 了解 Java 内置观察者模式
java.util
包内包含最基本的Observable 类与 Observer
接口,这和上面的 Subject
接口与 Observer
接口很类似。Observable
类与 Observer
接口使用起来更方便,因为许多的功能已经提供了。
3.2 Java 内置观察者模式如何运作
(1)如何把对象定义为观察者?
实现观察者 (Observer
) 接口,调用任何 Observable
对象的 addObserve()
方法。当不想要当观察者时,调用 deleteObserve()
方法。
(2)可观察者如何发送通知?
- 先调用
setChanged()
方法,标记状态已经被改变的事实。 - 调用
notifyObservers()
或notifyObservers(Object arg)
方法。
(3)观察者如何接收通知?
同以前一样,观察者实现了 update(Observable o, Object arg)
方法,只是方法签名不太一样。
3.3 重写气象观测站系统
主题类 WeatherData
package com.jas.jdk.observer;
import java.util.Observable;
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public void measurementsChanged(){
//在通知所有观察者之前,先调用 setChanged() 方法,用来表示状态已经改变
setChanged();
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getTemperature(){
return temperature;
}
public float getHumidity(){
return humidity;
}
public float getPressure() {
return pressure;
}
}
具体观察者对象,当前天气信息类 CurrentConditionsDisplay
package com.jas.jdk.observer;
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float pressure;
private Observable observable;
/**
* 通过构造函数,将当前对象记录为观察者
*
* @param observable 主题对象
*/
public CurrentConditionsDisplay(Observable observable){
this.observable = observable;
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions list : " + "温度 = " +
temperature + ", 湿度 = " + humidity + ", 气压 = " + pressure);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData) o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
this.pressure = weatherData.getPressure();
display();
}
}
}
气象站类 WeatherStation
(同上)
package com.jas.jdk.observer;
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
//ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
weatherData.setMeasurements(80,65,30.4f);
}
}
/**
* 输出
* Current conditions list : 温度 = 80.0, 湿度 = 65.0, 气压 = 30.4
*/
3.4 Java 内置观察者模式总结
Java 内置的观察者模式允许观察者有选择的获取数据,而不是主题对象强制将更新数据全部推送个每个观察者。
Observable
是一个类,并不是一个接口,这意味着你继承它的同时,不能再继承其他的类。在 Observable
类中 setChanged()
方法被保护了起来 (protected
),除非你继承该类,否则你无法创建 Observable
实例组合到你自己的对象中来。所以它违反了一个原则:“多用组合,少用继承”。
还有一点需要要注意的是:内置的观察者模式,观察者被通知的顺序并不是唯一的 (上面只定义了一个观察者),有时候并不能达到我们一开始的目的,你可以定义多个观察者验证一下。
根据具体的需求,如果 Java 内置的观察者模式 API 不能满设计,那么我们可以像刚开始那样自己实现一套观察者模式。
参考资料
《Head First 设计模式》
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。