ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2019-04-02 21:49:47  阅读:313  来源: 互联网

标签:HeadFirst void float 观察者 humidity 布告板 设计模式 public


1.声明

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

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

2.认识观察者模式

2.1观察者模式的生活案例

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

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

2.2观察者模式的定义

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

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

2.3关系图

2.4类图

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

Observer(观察者)

update():

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

Subject(消息发布者)

registerObserver():

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

removeObserver():

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

notifyObserver():

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

2.5松耦合的好处

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

设计原则

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

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

3.需求案例

3.1需求

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

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气象站类图

上图中,三个布告板不仅实现了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"。

标签:HeadFirst,void,float,观察者,humidity,布告板,设计模式,public
来源: https://blog.csdn.net/qq_32293345/article/details/88957088

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有