本文参照《Head First 设计模式》,转载请注明出处java
对于整个系列,咱们按照这本书的设计逻辑,使用情景分析的方式来描述,而且穿插使用一些问题,总结的方式来说述。而且全部的开发源码,都会托管到github上。 项目地址:github.com/jixiang5200…git
前一章主要讲解了设计模式入门和最经常使用的一个模式-----策略模式,并结合Joe的鸭子模型进行分析,想要了解的朋友能够回去回看一下。 这里咱们将继续介绍一种能够帮助对象知悉现状,不会错过该对象感兴趣的事。甚至对象能够本身决定是都要继续接受通知。有过设计模式学习经验的人会脱口而出-----观察者模式。对的,接下来咱们将了解一个新的设计模式,也就是观察者模式。github
最近你的团队获取了一个新的合约,须要负责创建一个Weather-O-Rama公司的下一代气象站----Internet气象观测站。 合约内容以下:编程
恭喜贵公司获选为敝公司创建下一代Internet气象观测站!该气象站必须创建在咱们专利申请的WeatherData对象上,由WeatherData对象负责追踪目前的天气情况(温度、湿度、气压)。咱们但愿贵公司能创建一个应用,有三种布告板,分别显示目前的情况、气象统计及简单的预报。当WeatherData对象获取到最新的测量数据时,三种布告板必须实时更新。 并且,这是一个能够拓展的气象站,Weather-O-Rama气象站但愿公布一组API,让其余开发人员能够写出本身的气象布告板,并插入此应用中咱们但愿贵公司能够提供这样的API。 Weather-O-Rama气象站有很好的商业运营模式:一旦客户上钩,他们使用每一个布告板都要付钱最好的部分就是,为了感谢贵公司创建此系统,咱们将以公司的认股权支付你。 咱们期待看到你的设计和应用的alpha版本。 附注:咱们正在通宵整理WeatherData源文件给大家。设计模式
根据开发的经验,咱们首先分析Weather-O-Rama公司的需求:api
咱们初始设计结构以下: bash
次日,Weather-O-Rama公司发送过来WeatherData的源码,其结构以下图 网络
其中measurementsChanged()方法在气象测试更新时,被调用。数据结构
首先,咱们从大部分不懂设计模式的开发者经常使用的设计方式开始。 根据Weather-O-Rama气象站开发人员的需求暗示,在measurementsChanged()方法中添加相关的代码:框架
public class WeatherData {
private float temperature;//温度
private float humidity;//湿度
private float pressure;//气压
private CurrentConditionsDisplay currentConditionsDisplay;//目前状态布告板
private StatisticsDisplay statisticsDisplay;//统计布告板
private ForecastDisplay forecastDisplay;//预测布告板
public WeatherData(CurrentConditionsDisplay currentConditionsDisplay
,StatisticsDisplay statisticsDisplay
,ForecastDisplay forecastDisplay){
this.currentConditionsDisplay=currentConditionsDisplay;
this.statisticsDisplay=statisticsDisplay;
this.forecastDisplay=forecastDisplay;
}
public float getTemperature() {
return temperature;
}
public float getHumidity(){
return humidity;
}
public float getPressure(){
return pressure;
}
//实例变量声明
public void measurementsChanged(){
//调用WeatherData的三个getter方法获取最近的测量值
float temp=getTemperature();
float humidity=getHumidity();
float pressure=getPressure();
currentConditionsDisplay.update(temp,humidity,pressure);
statisticsDisplay.update(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure);
}
//通知发生变化
public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humidity=humidity;
this.pressure=pressure;
measurementsChanged();
}
}
复制代码
回顾第一章的三个设计原则,咱们发现这里违反了几个原则
第一设计原则 找出应用中可能须要变化之处,把它们独立出来,不要和那些不须要变化的代码混合在一块儿。
第二设计原则 针对于接口编程,不针对实现编程
第三设计原则 多用组合,少用继承
在这里咱们使用了针对实现编程,而且没有将变化部分独立出来,这样会致使咱们之后在增长或删除布告板时必须修改应用程序。并且,最重要的是,咱们牺牲了可拓展性。
为了方便理解,咱们从平常生活中常遇到的情形来理解观察者模式,这里咱们使用生活常见的报纸和杂志订阅业务逻辑来理解:
从上面的逻辑咱们分析出,这里由如下部分组成,报社,用户,订阅。将其抽象出来就i是:出版者,订阅者,订阅。这里观察者模式的雏形已经出来了。
出版者+订阅者=观察者模式
若是上面已经理解了报社报纸订阅的逻辑,也能够很快知道观察者模式是什么。只是在其中名称会有差别,前面提到的“出版者”咱们能够称为**“主题(Subject)”或“被观察者(Observable)”(后一个更加经常使用),“订阅者”咱们称为“观察者(Observer)”**,这里咱们采用类UML的结构图来解释:
场景1: 某一天,鸭子对象以为本身的朋友都订阅了主题,本身也想称为一个观察者。因而告诉主题,它想当一个观察者。完成订阅后,鸭子也成为一个观察者了。
场景2: 老鼠对象厌烦了天天都被主题烦,决定从观察者序列离开,因而它告诉主题它想离开观察者行列,主题将它从观察者中除名。
上面的两个情形分别对应了注册和取消注册,这也是观察者模式最重要的两个概念。注册后的对象咱们才能够称为观察者。观察者取消注册后也不能称为观察者。
经过报纸业务和对象订阅的例子,咱们能够勾勒出观察者模式的基本概念。
观察者模式定义了对象之间的一对多的依赖,这样一来,当一个对象改变状态时,它全部的依赖者都会收到通知并自动更新。
主题/被观察者和观察者之间定义了一对多的关系。观察者依赖于主题/被观察者。一旦主题/被观察者数据发生改变的时候,观察者就会收到通知。那么,如何实现观察者和主题/被观察者呢?
因为网络上的实现观察者的方式很是多,咱们这里采起比较容易理解的方式Subject和Observer。对于更高级的使用方式,能够百度。 接下来咱们来看看基于Subject和Observer的类图结构:
到这里咱们再回到当初的问题,气象站中结构模型为一对多模型,其中WeatherData为气象模型中的“一”,而“多”也就对应了这里用来展现天气监测数据的各类布告板。相对于以前的针对实现的方式,使用观察者模式来设计会更加符合需求。优先咱们给出新的气象站模型。
依照前面的设计结构图,最终来实现具体代码结构
1.Subject
public interface Subject {
//注册观察者
public void registerObserver(Observer o);
//删除观察者
public void removeObserver(Observer o);
//当主题发生数据变化时,通知全部观察
public void notifyObservers();
}
复制代码
2.Observer
public interface Observer {
/**
*
* update:当气象站的观测数据发生改变时,这个方法会被调用
* @param temp 温度
* @param hunmidity 湿度
* @param pressure 气压
* @since JDK 1.6
*/
public void update(float temp,float hunmidity,float pressure);
}
复制代码
3.DisplayElement
public interface DisplayElement {
//当布告板须要展现时,调用此方法时
public void display();
}
复制代码
4.新的WeatherData1
public class WeatherData1 implements Subject{
private ArrayList<Observer> observers;
private float temperature;
private float humiditty;
private float pressure;
public WeatherData1(){
observers=new ArrayList<Observer>();
}
//注册
public void registerObserver(Observer o) {
observers.add(o);
}
//删除
public void removeObserver(Observer o) {
int i=observers.indexOf(o);
if(i>=0){
observers.remove(i);
}
}
//通知观察者数据变化
public void notifyObservers() {
for(int i=0;i<observers.size();i++){
Observer observer=observers.get(i);
observer.update(temperature, humiditty, pressure);
}
}
public void measurementsChanged(){
notifyObservers();
}
public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humiditty=humidity;
this.pressure=pressure;
measurementsChanged();
}
}
复制代码
5.CurrentConditionsDisplay
public class CurrentConditionsDisplay implements Observer,DisplayElement{
private float temperature;
private float humidity;
private float pressure;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData){
this.weatherData=weatherData;
weatherData.registerObserver(this);
}
/**
*
* update:更新布告板内容
* @author 吉祥
* @param temperature
* @param humidity
* @param pressure
* @since JDK 1.6
*/
public void update(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humidity=humidity;
this.pressure=pressure;
display();
}
/**
*
* display:展现布告板内容
* @author 吉祥
* @since JDK 1.6
*/
public void display(){
System.out.println("Current conditons:"+temperature
+"F degrees and "+humidity+"% humidity");
}
}
复制代码
6.ForecastDisplay
public class ForecastDisplay implements Observer,DisplayElement{
private float temperature;
private float humidity;
private float pressure;
private Subject weatherData;
public ForecastDisplay(Subject weatherData){
this.weatherData=weatherData;
weatherData.registerObserver(this);
}
/**
*
* update:更新布告板内容
* @author 吉祥
* @param temperature
* @param humidity
* @param pressure
* @since JDK 1.6
*/
public void update(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humidity=humidity;
this.pressure=pressure;
display();
}
/**
*
* display:展现布告板内容
* @author 吉祥
* @since JDK 1.6
*/
public void display(){
System.out.println("Forecast: More of the same");
}
}
复制代码
7.StatisticsDisplay public class StatisticsDisplay implements Observer,DisplayElement{ private float temperature; private float humidity; private float pressure; private Subject weatherData;
public StatisticsDisplay(SubjectweatherData){
this.weatherData=weatherData;
weatherData.registerObserver(this);
}
/**
*
* update:更新布告板内容
* @author 吉祥
* @param temperature
* @param humidity
* @param pressure
* @since JDK 1.6
*/
public void update(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humidity=humidity;
this.pressure=pressure;
display();
}
/**
*
* display:展现布告板内容
* @author 吉祥
* @since JDK 1.6
*/
public void display(){
System.out.println("Avg/Max/Min temperature= "+temperature
+"/"+temperature+"/"+temperature);
}
复制代码
}
ps:这里在Observer中使用Subject缘由在于方便之后的取消注册。
最后咱们创建一个测试类WeatherStation来进行测试
public class WeatherStation {
public static void main(String[] args){
WeatherData1 weatherData=new WeatherData1();
CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay=new StatisticsDisplay(weatherData);
ForecastDisplay forecastDisplay=new ForecastDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
复制代码
最终结果以下
是否可以在主题中提供向外的可让观察者本身获取本身想要数据,而并不是将全部的数据都推送给观察者?也就是在Push(推)的同时咱们也能够pull(拉)。
刚才的问题,其实熟悉Java语言的开发者会发现,在Java中已经有相应的模式,若是熟悉的能够直接跳过本章。 在java.util包下有Observer和Observable类,这两个类的结构跟咱们遇到的Subject和Observer模型有些相似。甚至但是随意使用push(推)或者pull(拉) 这里咱们使用在线的Java API网站在线Java API文档 首先查询Observer的API
这个与咱们所写的Observer结构几乎相同,只是在推送是把Observable类一块儿推送,这样用户既能够push也可使用pull的方式。那么Observable的结构呢
咱们发现这里Observable是类与咱们以前Subject做为接口的方式稍微有区别;而且Observable类其余方法更全。那么使用类的方式和使用接口的影响咱们在后面会继续讲。而且这里咱们关注setChanged()方法告诉被观察者的数据发生改变 那么,若是要使用Java中自带的观察者模式来修改原有气象站业务会如何。
首先,咱们来分析更改后气象站的模型:
相对于于以前Subject和Observer的模式,Java内置自带的观察者模式运行稍微有些差别。
将对象变成观察者只须要实现Observer(java.util.Observer)接口,而后调用任何Observable的addObserver()方法便可。若是要删除观察者,调用deleteObserver()便可。
被观察者若要推送通知,须要对象继承Observable(java.util.Observable)类,并先调用setChanged(),首先标记状态已经改变。而后调用notifyObservers()方法中的一个:notifyObservers()(通知观察者pull数据)或notifyObserers(Object object)(通知观察者push数据)
那么做为观察者如何处理被观察者推送出的数据呢。 这里逻辑以下:
在Observable类中setChanged()方法一开始我也有疑惑,为什么在推送以前须要调用该方法。后来查阅资料和Java API发现它很好的一个用处。咱们先来查看java的源码
那么利用气象站模型来实际操做一下,依照以前的模型咱们代码应该以下 1.WeatherData2
public class WeatherData2 extends Observable{
private float temperature;
private float humidity;
private float pressure;
//构造器不须要为了记住观察者创建数据模型
public WeatherData2(){
}
public void measurementsChanged(){
//在调用notifyObserver()须要指示状态已经更改了
setChanged();
//这里未使用notifyObserver(object),因此数据采用拉的逻辑
notifyObservers(this);
}
public void setMeasurements(float temperature,float humidity,float pressure){
this.temperature=temperature;
this.humidity=humidity;
this.pressure=pressure;
measurementsChanged();
}
//如下方法为pull操做提供
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
复制代码
2.CurrentConditionsDisplay1
public class CurrentConditionsDisplay1 implements Observer,DisplayElement{
private Observable observable;
private float temperature;
private float humidity;
private float pressure;
//构造器须要传入Observable参数,并登记成为观察者
public CurrentConditionsDisplay1(Observable observable){
this.observable=observable;
observable.addObserver(this);
}
//update方法增长Observable和数据对象做为参数
public void update(Observable o, Object arg) {
if(arg instanceof WeatherData2){
WeatherData2 weatherData2=(WeatherData2) arg;
this.temperature=weatherData2.getTemperature();
this.humidity=weatherData2.getHumidity();
this.pressure=weatherData2.getPressure();
display();
}
}
/**
*
* display:展现布告板内容
* @author 吉祥
* @since JDK 1.6
*/
public void display(){
System.out.println("Current conditons:"+temperature
+"F degrees and "+humidity+"% humidity");
}
}
复制代码
3.ForecastDisplay1
public class ForecastDisplay1 implements Observer,DisplayElement{
private float temperature;
private float humidity;
private float pressure;
private Observable observable;
public ForecastDisplay1(Observable observable){
this.observable=observable;
observable.addObserver(this);
}
public void update(Observable o,Object arg){
if(arg instanceof WeatherData2){
WeatherData2 weatherData2=(WeatherData2) arg;
this.temperature=weatherData2.getTemperature();
this.humidity=weatherData2.getHumidity();
this.pressure=weatherData2.getPressure();
display();
}
}
/**
*
* display:展现布告板内容
* @author 吉祥
* @since JDK 1.6
*/
public void display(){
System.out.println("Forecast: More of the same");
}
}
复制代码
4.StatisticsDisplay1
public class StatisticsDisplay1 implements Observer,DisplayElement{
private float temperature;
private float humidity;
private float pressure;
private Observable observable;
public StatisticsDisplay1(Observable observable){
this.observable=observable;
observable.addObserver(this);
}
public void update(Observable o,Object arg){
if(arg instanceof WeatherData2){
WeatherData2 weatherData2=(WeatherData2) arg;
this.temperature=weatherData2.getTemperature();
this.humidity=weatherData2.getHumidity();
this.pressure=weatherData2.getPressure();
display();
}
}
/**
*
* display:展现布告板内容
* @author 吉祥
* @since JDK 1.6
*/
public void display(){
System.out.println("Avg/Max/Min temperature= "+temperature
+"/"+temperature+"/"+temperature);
}
}
复制代码
最后进行测试: WeatherStation1
public class WeatherStation1 {
public static void main(String[] args){
WeatherData2 weatherData=new WeatherData2();
CurrentConditionsDisplay1 currentConditionsDisplay=new CurrentConditionsDisplay1(weatherData);
StatisticsDisplay1 statisticsDisplay=new StatisticsDisplay1(weatherData);
ForecastDisplay1 forecastDisplay=new ForecastDisplay1(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
复制代码
结果最终以下:
咱们对比以前Subject和Observer的观察者模式会发现二者输出顺序不同,这是为何?
其实java.util.Observable不依赖于观察者被通知的顺序的,而且实现了他的notifyObserver()方法,这会致使通知观察者的顺序不一样于Subject和Observer模型在具体类实现notifyObserver()方法。其实二者都没有任何的代码偏差,只是实现的方式不一样致使不一样的结果。
可是java.util.Observable类却违背了以前第一章中针对接口编程,而非针对实现编程。恐怖的是,它也没有接口实现,这就致使它的使用具备很高的局限性和低复用性。若是一个对象不只仅是被观察者,同时仍是另外一个超类的子类的时候,咱们没法使用多继承的方式来实现。咱们若是自行拓展的话,你会发现setChanged()方法是protected方法,这就表示只有java.util.Observable自身和其子类才可使用这个方法。这就违反了第二个设计原则---------"多用组合,少用继承"。这也是我通常不会使用Java自带的设计者模式的缘由。
如今比较流行的观察者模式,也就是RxJava,可是因为这个框架涉及不只仅有观察这模式,在以后整个设计模式整理玩不后,我会集中再讲。
到此,观察者模式的讲解已经所有讲解完成。总结一下。
第四设计原则 为交互对象之间的松耦合涉及而努力
观察者模式 在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。
相应的资料和代码托管地址github.com/jixiang5200…