观察者模式

文章说明

  • 该文章为《Head First 设计模式》的学习笔记
  • 非常推荐大家去买一本《Head First 设计模式》去看看,节奏轻松愉悦,讲得通俗易懂,非常适合想要学习、了解、应用设计模式以及OO设计原则的小白。

提出问题

  • 气象站会给我们一个WeatherData的类,类图如下(当然类图中还有其他的方法没有显现出来,这里只展现对我们来说最重要的部分):
WeatherData float temperature float humidity float pressure getTemperature() getHumidity() getPressure() measurementsChanged()
  • 很显然我们可以从这getTemperature() getHumidity() getPressure()三个方法中获取温度湿度压力的值
  • 气象站的程序员告诉我们这个measurementsChanged()方法会在气象台数据测量跟新后被自动调用
  • 我们的工作是实现measurementsChanged(),好让它更新目前状况气象统计天气预报的告示板。
  • 怎么才能设计一个牵一发而动全身的系统?当要WeatherData状态更新,所有的布告板都同步数据,并且系统的可扩展性可维护性要好,可以随时添加新的或则删除旧的布告板。

认识观察者模式

举一个十分容易理解的例子:

报纸和杂志订阅

  • 报社的业务就是初版报纸。
  • 向某家报社订阅报纸,只要他们有新报纸初版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。
  • 当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。
  • 只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或者取消订阅报纸。
  • 怎么样?是不是脑子里有了些观察者模式的概念了?
  • 报社 --> 气象站,报纸 --> WeatherData对象
  • 订阅报纸 --> 注册成为气象站的服务对象,给订户发新报纸的行为 --> 气象站更新了测量数据并发给了所有布告板
  • 订阅报纸或者取消订阅报纸 --> 添加或删除布告板
  • 初版者 + 订阅者 = 观察者模式
  • 气象站 + 布告板 = 观察者模式
  • 主题(可观察者) + 观察者 = 观察者模式

模式的定义

定义了对象之间一对多依赖,这样一来,当一个对象改变状态时,所有的依赖者都会收到通知并自动更新。

涉及的OO设计原则

  • 封装变化
  • 多用组合,少用继承
  • 针对接口编程,不针对实现编程
  • 为交互对象之间的松耦合设计而努力

自定义观察者模式

Java对观察者模式有内置的支持,这个我们稍后讲到,自己动手构建观察者模式并不难,而且更有弹性,因为内置的Observable(作为可观察者要扩展的类)并不是接口而是普通的类。

类图

«interface» Subject registerSubject() removeSubject() notifySubject() WeatherData float temperature float humidity float pressure registerObserver(Observer o) removeObserver(Observer o) notifyObserver() getTemperature() getHumidity() getPressure() mesuremetnsChanged() «interface» Observer update() «interface» DisplayElement dispaly() CurrentConditionsDisplay update() display() ForecastDisplay update() display()

上面的类图并没有指明主题观察者们怎么建立关系,也就是一对多的关系依赖,这里用到了松耦合的设计。

松耦合的设计

类图

«interface» Subject registerSubject() removeSubject() notifySubject() «interface» Observer update() CurrentConditionsDisplay update() display() ForecastDisplay update() display() 主题 主题 观察者

一对多关系

可以看到多个观察者对应一个主题,而同一个主题拥有多个观察者。

松耦合的实现

以气象台为例子

  • WeatherData通过集合拥有(has-a)多个布告板
List<Observer> observers;
  • 而布告板如CurrentConditionsDisplay通过有参构造函数获得weatherData的引用但不存储一份对象,仅仅使用它的方法注册或者注销成为观察者。
public CurrentConditionsDisplay(Subject weatherData) {
   weatherData.registerObserver(this); // 注册成为观察者
}
  • 当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此之间的细节。
  • 观察者模式提供了一种松耦合的设计,让主题和观察者之间送耦合。

很显然可以看出,WeatherData不需要知道你是谁?只需要知道你实现了Observer,而对于布告板来说,也不要知道你是谁?只需要知道你实现了Subject

部分代码的展示

主题WeatherData.java

/**
* 气象台提供的WeatherData类
* 包含了部分气象数据
* 作为观察者模式的主题
*/
public class WeatherData implements Subject {
   private List<Observer> observers;
   private float temperature, humidity, pressure;
   public WeatherData() {
   	observers = new ArrayList<Observer>();
   }
   // 注册成为观察者
   public void registerObserver(Observer o) {
   	// 不可以重复注册,这里可以使用Set<Observer>
   	if(observers.contains(o))
   		return ;
   	observers.add(o);
   	System.out.println("恭喜 " + o + " 注册策成为我的观察者之一");
   	System.out.println("观察者们:" + observers);
   }
   // 注销成为观察者
   public void removeObserver(Observer o) {
   	if(observers.contains(o))
   		observers.remove(o);
   	System.out.println("很高兴为您服务!" + o + "随时欢迎您再加入观察者联盟~");
   	System.out.println(observers);
   }
   // 当状态改变时,告知所有观察者
   public void notifyObserver() {
   	for(Observer o : observers)
   		o.update(temperature, humidity, pressure);
   }
   // 当新的测量数据备妥时,会自动被调用(我们不在乎方法如何被调用,我们只在乎它被调用了)
   public void measurementsChanged() {
   	notifyObserver();
   }
   // 设置气象数据
   public void setMeasurements(
   	float temperature, float humidity, float pressure) {
   	this.temperature = temperature;
   	this.humidity = humidity;
   	this.pressure = pressure;
   	notifyObserver();
   }
   // WeatherData的其他方法...
}

Interfaces: Subject,Observer,DisplayElement

public interface Subject {
	public void registerObserver(Observer o);
	public void removeObserver(Observer o);
	public void notifyObserver();
}
public interface Observer {
	void update(float temp, float humi, float pres);
}
public interface DisplayElements {
	void display();
}

观察者CurrentCoditionsDisplay.java
这里仅仅展示其中一个布告板

/**
 * 创建目前状况的布告板
 * 即展示当前的温度,湿度,气压
 */
public class CurrentConditionDisplay implements Observer, DisplayElements {
	private float temperature, humidity, pressure;
	public CurrentConditionDisplay(Subject weatherData) {
		weatherData.registerObserver(this);
	}
	public void display() {
		System.out.println(
				"目前状况:温度[" + temperature + 
				"F] 湿度[" + humidity + 
				"%] 气压[" + pressure +  "Pa]");
	}
	public void update(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		display();
	}
	@Override
	public String toString() {
		return getClass().getSimpleName();
	}
}

测试类WeatherStation.java

public class WeatherStation {
	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
		CurrentConditionDisplay ccd = 
			new CurrentConditionDisplay(weatherData);
		StatisticsDisplay sd =
			new StatisticsDisplay(weatherData);
		ForecastDisplay fd = 
			new ForecastDisplay(weatherData);
		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
	}
}

运行结果

恭喜 CurrentConditionDisplay 注册策成为我的观察者之一
观察者们:[CurrentConditionDisplay]
恭喜 StatisticsDisplay 注册策成为我的观察者之一
观察者们:[CurrentConditionDisplay, StatisticsDisplay]
恭喜 ForecastDisplay 注册策成为我的观察者之一
观察者们:[CurrentConditionDisplay, StatisticsDisplay, ForecastDisplay]
目前状况:温度[80.0F] 湿度[65.0%] 气压[30.4Pa]
统计:温度[平均值/最大值/最小值] : 80.0/80.0/80.0
预测:天气在好转
目前状况:温度[82.0F] 湿度[70.0%] 气压[29.2Pa]
统计:温度[平均值/最大值/最小值] : 81.0/82.0/80.0
预测:小心阴雨天气
目前状况:温度[78.0F] 湿度[90.0%] 气压[29.2Pa]
统计:温度[平均值/最大值/最小值] : 80.0/82.0/78.0
预测:天气没有什么变化

Java内置的观察者模式

如何运作?

观察者

  • 首先观察者实现java.util.Observer接口。
  • 然后调用任何Observable对象的addObserver()方法。
  • 当不再想当观察者时,调用deleteObserver()方法。

可观察者

  • 扩展即继承java.util.Observable类,注意这是一个类。

可观察者怎么送出通知…

  • 先调用setChange()方法,标记状态已经改变的事实。
  • 然后调用两种notifyObservers()方法中的一种:
  • notifyObservres() 或者 notifyObservers(Object arg)
  • 一种是"",另一种是""两种方式都可以,但是""配合setter方法一起用效果更佳。

观察者如何收到通知…

  • 观察者实现了更新方法,方法的特征签名稍有不同:
  • update(Observable o, Object arg)
  • 当可观察者用""的方式发送消息时,Object arg 则为null

关于setChange()方法

  • setChange()方法不是可有可无的,它的存在可以更好的控制发送消息的行为,有更多的弹性。
  • clearChange()方法,将changed状态设置回false
  • hasChanged()方法,告诉你changed标志的当前状态。

部分代码展示

Java内置支持的方式重做气象站的例子
可观察者WeatherData.java

import java.util.Observable;
/**
 * 用java内置的支持实现观察者模式
 * 让WeatherData扩展Observable成为可观察者,也就是主题
 */
public class WeatherData extends Observable {
	private float temperature, humidity, pressure;
	public WeatherData() {}
	public void measurementsChanged() {
		setChanged(); // 改变状态标志
		notifyObservers();
	}
	public void setMeasurements(
		float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		measurementsChanged();
	}
	// 要使用“拉”的做法,定义setter方法
	public float getTemperature() {
		return temperature;
	}
	public float getHumidity() {
		return humidity;
	}
	public float getPressure() {
		return pressure;
	}
}

观察者CurrentConditionDisplay.java
这里仅仅展示其中一个布告板

import java.util.Observable;
import java.util.Observer;
/**
 * 用"拉"的做法重写目前状况的布告板
 */
public class CurrentConditionDisplay implements Observer, DisplayElements {
	private float temperature, humidity, pressure;
	public CurrentConditionDisplay(Observable observable) {
		// 注册成为观察者
		observable.addObserver(this);
	}
	public void display() {
		System.out.println(
				"目前状况:温度[" + temperature + 
				"F] 湿度[" + humidity + 
				"%] 气压[" + pressure +  "Pa]");
	}
	public void update(Observable o, Object arg) {
		// 这里o是指扩展了Observable的WeatherData类
		if(o instanceof WeatherData) { // "拉"的实现
			WeatherData weatherData = (WeatherData)o;
			temperature = weatherData.getTemperature();
			humidity = weatherData.getHumidity();
			pressure = weatherData.getPressure();
			display();
		}
	}
	@Override
	public String toString() {
		return getClass().getSimpleName();
	}
}

运行结果

统计:温度[平均值/最大值/最小值] : 80.0/80.0/200.0
预测:天气在好转
目前状况:温度[80.0F] 湿度[65.0%] 气压[30.4Pa]
统计:温度[平均值/最大值/最小值] : 81.0/82.0/200.0
预测:小心阴雨天气
目前状况:温度[82.0F] 湿度[70.0%] 气压[29.2Pa]
统计:温度[平均值/最大值/最小值] : 80.0/82.0/78.0
预测:天气没有什么变化
目前状况:温度[78.0F] 湿度[90.0%] 气压[29.2Pa]
  • 可以看到通知观察者的次序不同我们先前的次序
  • 所以不能依赖于观察者被通知的次序

黑暗面

  • java.util.Observable是一个类,而且还不是抽象类
  • 这意味着我们不能创建自己的可观察者。
  • 并且当我们想同时拥有Observable类和另一个超类的行为,比如我是一个可观察者并且我也是学生,但是这做不到,因为Java不支持多继承。
  • Observable还将setCahgned()保护起来(定义为protected),这意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的类中。这违反了"多用组合,少用继承"的设计原则。

观察者模式是JDK中使用最多的模式

例如:

  • JavaBeans
  • Swing
  • RMI
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值