一、观察者模式
《Head First Design Patterns》: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.
即:观察者模式在对象之间定义了一对多的依赖,当一个对象状态改变了,它的全部依赖都会自动被通知和更新。
简单说,就是一个对象状态改变后,它所依赖的对象全部要更新。
类图:
二、观察者模式案例
一家天气预报公司,会记录三种天气状态:气温、湿度、气压,当监察到状态改变时(忽略几秒钟还是几分钟这些细节),会在三台显示器上将最新天气情况显示出来,分别是显示气温、湿度和气压,显示最高气温、平均气温和最低气温,显示天气预报(下雨、下雪、晴天等)。
版本一:只有一个WeatherData类,获取到最新温度、湿度、气压时,会自动调用measurementChanged方法。
public class WeatherData {
public void measurementsChanged() {
//假定getTemperature、getHumidity、getPressure可以获取最新状态
double temp=getTemperature();
double humidity=getHumidity();
double pressure=getPressure();
//第一台显示
currentConditionDisplay.update(temp,humidity,pressure);
//第二台显示
statisticsDisplay.update(temp,humidity,pressure);
//第三台显示
forecastDisplay.update(temp,humidity,pressure);
}
}
分析:温度、湿度、气压应该封装为成员变量,三条update方法直接通过调用来编写,就等于是面向实现编程而非面向接口编程了。
版本二:采用观察者模式来解决,创建观察者接口,同时有多个观察者实现类,创建主题类接口,主题实现类(WeatherData)持有观察者接口类型的集合,通过集合,可以增到依赖对象中,也可以移除对象。
//主题类接口,包括注册新增观察者、移除观察者、通知观察者方法
interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
//具体的主题类
public class WeatherData implements Subject {
private double temperature,hummidity, pressure;
private List<Observer> list=new ArrayList<>();
public void measurementsChanged() {
//假定以下三行是最新测量出来的数据
temperature=temperature+1;
hummidity=hummidity-1;
pressure=pressure+1;
notifyObservers();//通知观察者
}
//新增观察者
@Override
public void registerObserver(Observer o) {
list.add(o);
}
//删除观察者
@Override
public void removeObserver(Observer o) {
list.remove(o);
}
//发生改变后,通知观察者
@Override
public void notifyObservers() {
for(Observer o: list) {
o.update(temperature, hummidity, pressure);
}
}
public static void main(String[] args) {
WeatherData weather=new WeatherData();
weather.measurementsChanged();
Observer display1 =new CurrentConditionDiplay(weather);
Observer display2 =new WeatherStatusDiplay(weather);
Observer display3 =new ForecastDisplay(weather);
weather.measurementsChanged();
weather.removeObserver(display2);//取消display2
System.out.println("==取消2号显示器后==");
weather.measurementsChanged();
}
}
//显示信息的接口,这个不是重点
interface Displayment {
public void display();
}
//观察者接口
interface Observer {
public void update(double temperature, double hummidity, double pressure);
}
//具体观察者1:当前天气情况
class CurrentConditionDiplay implements Displayment, Observer {
private Subject weather;
private double temperature,humidity,pressure;
public CurrentConditionDiplay(Subject weather) {
this.weather=weather;
weather.registerObserver(this);//自动订阅
}
@Override
public void update(double temperature, double hummidity, double pressure) {
this.temperature=temperature;
this.humidity=hummidity;
this.pressure=pressure;
display();
}
@Override
public void display() {
System.out.println("Current Conditions:");
System.out.println("Temp:"+temperature+"°");
System.out.println("Humidity:"+humidity);
System.out.println("Pressure:"+pressure+"hPa");
}
}
//具体观察者2:天气状态显示
class WeatherStatusDiplay implements Displayment, Observer {
private Subject weather;
private double temperature;
public WeatherStatusDiplay(Subject weather) {
this.weather=weather;
weather.registerObserver(this);
}
@Override
public void update(double temperature, double hummidity, double pressure) {
this.temperature=temperature;
display();
}
@Override
public void display() {
System.out.println("Weather Status:");
System.out.println("Avg.temp: "+temperature+"°");
System.out.println("Min.temp: " +temperature+"°");
System.out.println("Max.temp: "+temperature+"°");
}
}
//具体观察者3:预测天气
class ForecastDisplay implements Displayment, Observer {
private Subject weather;
private double temperature,hummidity,pressure;
public ForecastDisplay(Subject weather) {
this.weather=weather;
weather.registerObserver(this);
}
@Override
public void update(double temperature, double hummidity, double pressure) {
this.temperature=temperature;
this.hummidity=hummidity;
this.pressure=pressure;
display();
}
@Override
public void display() {
if(temperature<=0) {
System.out.println("下雪");
} else if(hummidity<=20&&pressure>=100) {
System.out.println("下雨");
} else {
System.out.println("晴天");
}
}
}
类图:
分析:
- 写好观察者接口,具体观察者实现该接口,即面向接口编程。
- 编写主题接口,具体主题实现该接口,同时持有观察者接口类型集合的引用,可以加入新的观察者,也可以移除观察者。
- 在具体观察者类中,持有主题类接口类型的引用,在构造方法中,传入参数也是主题类接口类型,同时通过主题类将当前类加入到观察者集合中。
- 当主题类状态发生变化时,遍历观察者集合,调用观察者的方法。
版本三:通过JavaAPI提供的观察者接口(java.util.Observer)和主题类(java.util.Observable)来实现
java.util.Observer观察者接口:
public interface Observer {
/**
* o 主题对象
* arg 参数
*/
void update(Observable o, Object arg);
}
java.util.Observable类:
public class Observable {
private boolean changed = false;//标志是否发生改变
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
//增加观察者
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//移除观察者
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
//通知观察者
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
具体代码:
//具体主题类,直接继承java.util.Observable类
public class WeatherData extends Observable {
private double temperature, humidity, pressure;
public WeatherData() {
}
public void measurementsChanged() {
setChanged();
notifyObservers();
}
public void setMeasurements(double t, double h, double p) {
temperature=t;
humidity=h;
pressure=p;
measurementsChanged();
}
public double getTemperature() {
return temperature;
}
public double getHumidity() {
return humidity;
}
public double getPressure() {
return pressure;
}
public static void main(String[] args) {
WeatherData weather=new WeatherData();
Observer display1 =new CurrentConditionObserver(weather);
Observer display2 =new WeatherStatusObserver(weather);
Observer display3 =new ForecastDisplay(weather);
weather.setMeasurements(30, 60, 50);
weather.deleteObserver(display1);
weather.setMeasurements(24, 70, 20);
}
}
interface DisplayElement {
void display();
}
//实现java.util.Observer观察者接口和DisplayElement显示接口
class CurrentConditionObserver implements Observer, DisplayElement {
private Observable observable;
private double temperature;
private double humidity;
public CurrentConditionObserver(Observable observable) {
this.observable=observable;
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherData) {
WeatherData w=(WeatherData) o;
temperature=w.getTemperature();
humidity=w.getHumidity();
display();
}
}
@Override
public void display() {
System.out.println("Current conditions: "+temperature
+"F degrees and "+humidity+"% humidity");
}
}
class WeatherStatusObserver implements Observer, DisplayElement {
private Observable observable;
private double temperature;
public WeatherStatusObserver(Observable observable) {
this.observable=observable;
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherData) {
WeatherData w=(WeatherData) o;
temperature=w.getTemperature();
display();
}
}
@Override
public void display() {
System.out.println("Weather Status:");
System.out.println("Avg.temp: "+temperature+"°");
System.out.println("Min.temp: " +temperature+"°");
System.out.println("Max.temp: "+temperature+"°");
}
}
class ForecastDisplay implements Observer {
private Observable observable;
private double temperature, humidity, pressure;
public ForecastDisplay(Observable observable) {
this.observable=observable;
observable.addObserver(this);
}
@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();
}
}
public void display() {
if(temperature<=0) {
System.out.println("下雪");
} else if(humidity<=20&&pressure>=100) {
System.out.println("下雨");
} else {
System.out.println("晴天");
}
}
}
分析:
- 注意到Obserable主题类是一个class而不是interface,违背了面向接口编程的原则;
- 倘若使用组合来代替继承,而clearChanged和setChanged方法都是protected修饰,意味着只能同包或者子类可以访问该方法,组合除非在同一包下,否则不能使用,违背了组合优于继承。因此,如果有必要的话,可以自行实现一个带有接口的Observable类。
三、观察者模式应用场景
订阅服务,如订阅报纸、订阅电台、订阅图书、订阅天气预报......
Java Swing中的监听机制就是典型的观察者模式例子,某个组件可以通过addActionListener增加多个观察者,当事件触发时,所有的监听类的监听方法actionPerformed都会执行。
浅谈观察者模式中使用到的面向对象设计原则:
- 分离变化、封装变化:观察者是容易变化的,因此要分离出来,封装成观察者类。
- 面向接口编程:将所有观察者类继承唯一一个观察者接口,在主题类中的List中使用接口类型,这样观察者类的变化不会引起主题类的变化,因为接口是稳定的,而具体的类是不稳定的(容易改变)。
- 组合优于继承:主题类中使用了List<Observer>,意味着使用了一对多的组合。为什么组合优于继承?首先,Java只支持单继承,继承是稀缺的,不要轻易使用;其次,组合比继承有更好的可测试性,继承需要测试父类和派生类,比较麻烦;再次,组合比继承更加灵活,在设计模式的策略模式和装饰模式中,已经有了很好的证明。