《HeadFirst设计模式》第二章-观察者模式1

1.声明

设计模式中的设计思想、图片和部分代码参考自《Head First设计模式》作者Eric Freeman & Elisabeth Freeman & Kathy Siezza & Bert Bates

在这里我只是对这本书进行学习阅读,并向大家分享一些心得体会。

2.认识观察者模式

2.1观察者模式的生活案例

在观察者模式当中,分为观察者和信息发布者,当信息发布者想要发布一些消息时,那么这些观察者就都能接收到这些消息。

在现实生活中就有观察者模式的使用案例,例如社会上的报社,报社的任务是出版报纸,人们如果对这家报社出版的报纸感兴趣,那么就会订阅这家报社的消息,当这家报社出新的报纸时,这家报社的众多订阅者都会接收到新的报纸,当然如果对报社的消息不再感兴趣。那么随时可以取消订阅,那么这个人便再也不会收到报社的消息了。

2.2观察者模式的定义

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

出版者(主题Subject) + 订阅者(观察者Observer) = 观察者模式

2.3关系图

《HeadFirst设计模式》第二章-观察者模式1

2.4类图

体现了面向接口(超类型)编程的设计方式:

《HeadFirst设计模式》第二章-观察者模式1

Observer(观察者)

update():

观察者通过本类中的这个方法,从外界获得自己感兴趣的消息。

Subject(消息发布者)

registerObserver():

注册观察者,即将观察者的引用放到本类的集合中,当需要向外发布消息的时候,会遍历本类中的集合,挨个拿出观察者对象,并调用这些对象的update()将消息放入各个观察者的类中。

removeObserver():

移除观察者,当观察者对消息不再感兴趣时,消息发布者会将这个观察者的对象的引用,从观察者集合中移除。

notifyObserver():

遍历观察者集合,依次调用对象的update()进行消息通知。

2.5松耦合的好处

在观察者模式中,我们可以感受到松耦合的好处,即消息发布者并不关心观察者的类的细节,只要求消息发布者是Observer就好了,这样无论有老的观察者取消消息的订阅,或者有新的观察者订阅了消息,都不会对主题Subject产生影响。同理Subject的变化也不会影响订阅者,因为Observer和Subject是松耦合的。

设计原则

为了交互对象之间的松耦合设计而努力。

松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

3.需求案例

3.1需求

现在有个气象站有如下需求,需要我们帮他们把检测到的原始数据显示到布告板上,并且他们会提供一个对象(WeatherData),这个对象可以获取到气象站的原始数据,我们再将这些原始数据显示到布告板上。气象站目前要求了三种布告板:目前状况(温度、湿度、气压),气象统计天气预报,另外气象站还要求我们的API设计必须具有可扩展性,方便日后其它布告板的添加,总体需求展现为下图:

《HeadFirst设计模式》第二章-观察者模式1

3.2气象站提供的API

气象站说我们可以通过WeatherData拿到原始数据,那么我们可以看看这个对象中有哪些方法。

//气象站提供的获取原始数据的类
public class WeatherData {

	//获得温度数据
	public float getTemperature() {
		System.out.println("模拟获取温度数据的过程...");
	}
	
	//获得湿度数据
	public float getHumidity() {
		System.out.println("模拟获取湿度数据的过程...");
	}
	
	//获得气压数据
	public float getPressure() {
		System.out.println("模拟获取气压数据的过程...");
	}
	
	/*
	 * 一旦气象测量更新,此方法会被调用
	 * 相当于报社有新的报纸,所以通知观察者的方法应该写在这个方法里
	 * 而上面三个方法,我们并不需要关心细节,直接拿来用就可以了
	 * */
	public void measurementsChanged() {
		// TODO 需要我们书写的部分
	}
}

3.3错误的示范

那么看一个错误的measurementsChanged()实现:

注:

currentConditionsDisplay(目前状况)、statisticsDisplay(气象统计)、forecastDisplay(天气预报)分别是三个布告板(即:三个观察者)。

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); 
}

上面的代码的问题是:

气象公司已经强调过,布告板以后可能会增加新的,或者有可能去掉某个旧的布告板,那么这样修改还是需要更改源代码,三个布告板对象全部都硬编码到程序中,这仍然是针对实现编程,我们需要改为针对接口编程。

3.4采用观察者模式

3.4.1气象站类图

《HeadFirst设计模式》第二章-观察者模式1

上图中,三个布告板不仅实现了Observer接口,成为了观察者。还实现了DisplayElement接口,用于展示布告板的数据。

3.4.2气象站代码实现

消息发布者接口:

//消息发布者的接口
public interface Subject {

	//注册观察者
	public void registerObserver(Observer o);
	
	//移除观察者
	public void removeObserver(Observer o);
	
	//当主题状态改变时,此方法会被调用,用来通知观察者
	public void notifyObservers();
}

观察者接口:

//观察者接口
public interface Observer {

	//Subject通过本类中的Observer引用,调用update方法,将消息传递到Observer类中
	public void update(float temp, float humidity, float pressure);
}

展示接口:

//展示接口
public interface DisplayElement {

	//展示方法
	public void display();
}

消息发布者的实现类WeatherData:

//可以提供基础气象数据的类
public class WeatherData implements Subject{
	
	//Subject端保留的观察者集合,推送消息时,需要遍历这个集合
	private ArrayList<Observer> observers;
	//温度
	private float temperature;
	//湿度
	private float humidity;
	//气压
	private float pressure;
	
	public WeatherData() {
		observers = new ArrayList<Observer>();
	}

	@Override
	public void registerObserver(Observer o) {
		// 观察者集合注册观察者
		observers.add(o);
	}

	@Override
	public void removeObserver(Observer o) {
		// 观察者集合中移除观察者
		observers.remove(o);
	}

	@Override
	public void notifyObservers() {
		//遍历观察者集合
		for (Observer observer : observers) {
			//用观察者对象的引用,调用他自身的update方法,将数据传入观察者所在的类
			observer.update(temperature, humidity, pressure);
		}
	}

	//前面提到,气象站数据发生变化时,会调用此方法
	public void measurementsChanged() {
		//消息发布
		notifyObservers();
	}
	
	//手动设置温度、湿度、气压数据,模拟气候变化(其实时气象站内部的代码)
	public void setMeasurements(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		measurementsChanged();
	}
}

观察者的实现类-目前状况布告板:

//观察者的实现类-目前温度
public class CurrentConditionsDisplay implements Observer, DisplayElement{

	//温度
	private float temperature;
	//湿度
	private float humidity;
	//气压
	private float pressure;
	//Subject的引用,保留此引用的作用:可以通过这个引用干其他的操作,例如取消注册
	private Subject weatherData;
	
	public CurrentConditionsDisplay(Subject weatherData) {
		this.weatherData = weatherData;
		//虽然目前只利用weatherData进行了注册操作
		weatherData.registerObserver(this);
	}
	
	//更新数据
	@Override
	public void update(float temp, float humidity, float pressure) {
		//接收数据
		this.temperature = temp;
		this.humidity = humidity;
		this.pressure = pressure;
		//展示数据
		display();
	}

	//展示数据
	@Override
	public void display() {
		System.out.println("当前温度: " + temperature 
			+ "当前湿度:" + humidity 
			+ "气压:" + pressure);
	}

}

观察者的实现类-气象统计:

//观察者实现类-气象统计
public class StatisticsDisplay implements Observer, DisplayElement{
	
	//最高温度
	private float maxTemp = 0.0f;
	//最低温度
	private float minTemp = 200;
	//温度和
	private float tempSum= 0.0f;
	//气象更新次数
	private int numReadings;
	//消息发布者
	private WeatherData weatherData;
	
	public StatisticsDisplay(WeatherData weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}

	@Override
	public void update(float temp, float humidity, float pressure) {
		tempSum += temp;
		numReadings++;
		//得到最高温度
		if (temp > maxTemp) {
			maxTemp = temp;
		}
		//得到最低温度
		if (temp < minTemp) {
			minTemp = temp;
		}
		
		display();
	}
	
	@Override
	public void display() {
		System.out.println("平均/最高/最低   温度 = " + (tempSum / numReadings)
				+ "/" + maxTemp + "/" + minTemp);
	}

}

注:天气预告布告板这里省略。

测试类:

//气象显示测试类(天气预报同理,所以在这里省略)
public class WeatherStation {
	
	public static void main(String[] args) {
		WeatherData weatherData = new WeatherData();
		
		//目前温度布告板订阅消息(注:ccd刚出生就注册了WeatherData)
		CurrentConditionsDisplay ccd = new CurrentConditionsDisplay(weatherData);
		//气象统计布告板订阅消息(注:sd刚出生就注册了WeatherData)
		StatisticsDisplay sd = new StatisticsDisplay(weatherData);
		
		//模拟气象变化:第一次变化
		weatherData.setMeasurements(71, 72, 73);
		
		System.out.println("---------------------------------------------------");
		
		//模拟气象变化:第二次变化
		weatherData.setMeasurements(81, 82, 83);
	}

}

运行结果:

当前温度: 71.0,当前湿度:72.0,气压:73.0
平均/最高/最低   温度 = 71.0/71.0/71.0
---------------------------------------------------
当前温度: 81.0,当前湿度:82.0,气压:83.0
平均/最高/最低   温度 = 76.0/81.0/71.0

总结

这样即使有新的布告板,那么我们只需要让这个新的布告板订阅这个消息即可。

但是,目前我们设计的观察者模式中,观察者完全处于被动状态,即只有消息发布者准备好了之后,才会将消息推送出去,在这之前,观察者对消息的进度毫不知情,所以这种观察者模式是一种“推送消息”的模式,那么有没有一种观察者主动取“拉取消息”的模式呢?

可参见下篇文章"《HeadFirst设计模式》第二章-观察者模式2"。

上一篇:设计模式-命令模式(Command)


下一篇:node pressure and pod eviction