最近在阅读HEAD+FIRST+设计者模式时,认为其中的事例非常有趣,于是希望在看的过程中,跟随书直接将代码演示,增进理解。同时将一些内容放到自己的博客中,方便日后查阅,或者其他小伙伴们看。

观察者模式要点:

1、观察者模式定义了对象之间一对多的关系

2、主题(也就是可观察者)用一个共同的接口来更新观察者

3、观察者和可观察者之间用松耦合方式结合(loosecoupling),可观察者不知道观察者的细节,只知道观察者实现了观察者接口

4、使用此模式时,你可从被观察者推(push)或拉(get)数据(然而,推的方式被认为更“正确”)。

5、有多个观察者时,不可以依赖特定的通知次序。

6、Java有多种观察者模式的实现,包括了通用的Java.util.Observable。

7、要注意Java.util.Observable实现上所带来的一些问题

8、如果有必要的话,可以实现自己的Observable,这并不难,不要害怕

9、Swing大量使用观察者模式,许多GUI框架也是如此。

10、此模式也被应用在许多地方,例如:JavaBeans、RMI。

观察者模式实例讲解以一个工作合约开始:

恭喜贵公司获选为敝公司建立下一代Internet气象观测站!该气象站必须建立在我们专利申请中的WeatherData对象上,由WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。我们希望贵公司能建立一个应用,有三种布告板,分别显示目前的状况、气相统计以及简单的预报。当WeatherData对象获得最新的测量数据时,是那种布告板必须实时更新。

而且这是一个可扩展的气象站,weather-o-Rama气象站希望公布一组API,好让其他开发人员可以写出自己的气象布告板,并插入此应用中。我们希望贵公司能提供这样的API。

weather-o-Rama气象站有很好的的商业运营模式:一旦客户上钩,他们使用每个布告板都要付钱。最好的部分就是,为了感谢贵公司建立此系统,我们将以公司的认股权支付你。

需求分析:

如上描述了客户的需求,经过分析:

WeatherData对象知道如何跟物理气象站联系,以取得更新的数据。weatherData对象取得数据,并更新三个布告板。

WeatherData对象有三个方法,getTemperature()、getHumidity()、getPressure() 方法分别获取这些值,

mentsChanged()方法可以通知外界,数值发生变化了!而我们的代码就要在该方法中被调用

当前已知环境:

1、WeatherData 类具有getter方法,可以取得测量值:温度、湿度与气压

2、当新的测量数据备妥时,measurementsChanged()方法就会被调用(我们不在乎此方法是如何被调用的,我们只在乎它被调用了)。

3、我们需要实现三个使用天气数据的布告板:“目前状况“布告、”气相统计”布告、“天气预报”布告。一旦WeatherData有新的测量,这些布告必须马上更新。

4、此系统必须可扩展,让其他开发人员建立定制的布告板,用户可以随心所欲地添加或删除任何布告板,目前初始的布告板有三类:“目前状况”布告、“气象监测”布告、“天气预告”布告

先简单以一个不妥的方案来简单示意一下,之后再提供完整的设计

    //不妥方案示例public void measurementsChanged(){float temp=getTempreture();int hum=getHumidity();float pressure=getPressure();CurrentConditionsDisplay.update(temp,hum,pressure);ForeCastConditionDisplay.update(temp,hum,pressure);SatisfyConditionDisplay.update(temp,hum,pressure);}

如上发现,该设计存在多种问题,代码移植困难,不具备可扩展性。按照“变化的进行封装”、“针对接口编程而不是实现编程”的原则,经过仔细分析,按照基本设计原则进行修改调整,最终完成,发现其符合观察者模式的条件:

1、气象数据对策和布告板具备一对多的关系,并且后者紧密依赖前者

2、一个气象数据对象掌握者某些数据状态的变化情况,而布告板需要这些状态值

3、 布告板可能有多种类型,但是有一些基本的特征,即他们都需要调用数据更新的方法。

4、布告板需要实时地关注该对象

(如上理解只是个人理解,描述不恰当的地方还请指正)

鉴于各种原因,先贴上代码

package com.example.zhouxueli.myapplication.ObserverDesign;public interface Subject {
/**
* 观察者模式中的主题,
* 观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,
* 它的所有依赖者都会受到通知并自动更新,而此处的subject就是其中的一,
* 此处的主题接口,对象使用此接口注册为观察者,或者把自己从观察者中删除
*
* 一个具体主题总是实现主题接口,除了注册和撤销方法之外,具体主题还实现了
* notifyObservers(_)方法,此方法用于在状态改变是更新所有当前观察者
*
* 具体主题也可能有设置和获取状态的方法(稍后会进一步讨论)
* *///某对象(观察者)可以通过该方法,将自己注册为该主题的观察者void registerObserver(Observer observer);//某观察者可以通过该方法,将自己从该主题的观察者中解除void removeObserver(Observer observer);//该方法用于主题通知其观察者们某方法发生了变化void notifyObservers();
}
package com.example.zhouxueli.myapplication.ObserverDesign;public interface Observer {/*** 观察者模式中的观察者* 每个主题可以有许多观察者* 所有潜在观察者必须实现观察者接口,这个接口只有update()一个方法,当主题状态改变时它被调用* 具体的观察者可以是实现此接口的任意类。观察者必须注册具体主题,以便接收更新*//*** 想要将自己作为Subject的观察者,则必须实现该接口,* 因为subject 只能通过该方法通知观察者某数据发生变化*/void update(float temp,int humidity,float pressure);
}
package com.example.zhouxueli.myapplication.ObserverDesign;public interface DisplayElement {/**该方法为无关紧要的方法,用于更形象地辅助描述观察者模式,* 该方法和观察者模式无任何关系,只是让读者更生动深入现场* 以更接近实战开发*/void show();
}
package com.example.zhouxueli.myapplication.ObserverDesign;import java.util.ArrayList;public class WeatherDataSubject implements Subject {/*** 气象监测对象  该类演示了观察者模式中的主题角色* 因为该类持有气温、气压等状态值的控制权(可以获取改状态值,提供给其他公告板)* 当气温、气压发生变化时,该气象监测对象会通知其他用于显示气温、气压内容的对象该值发生* 了变化,各个展示系统或者其他应用到该数据的对象据此自行进行相应处理,比如展示新的* 气温、气压值* 该*///加上一个ArrayList 来记录观察者,此ArrayList是在构造器中建立的private ArrayList observers;//如下表示三个可变的状态值,该值会动态变化,subject在如下三个值//发生变化时会告知其观察者们private float tempreture;private int humidity;private float pressure;public WeatherDataSubject(){observers = new ArrayList();}/*** subject 可用此方法 将某个对象作为自己的观察者* @param Observer 一个实现了观察者接口的具体观察者对象** */@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}/*** subject 可将某个观察者从自己的观察者对象列表中移除* @param Observer 一个实现了观察者接口的具体观察者对象** */@Overridepublic void removeObserver(Observer observer) {int i = observers.indexOf(observer);if (i >= 0){observers.remove(i);}observers.remove(observer);}/*** 主题subject通过该方法通知其所有观察者某数据发生变化了* 我们把状态高速每一个观察者,因为观察者都实现了update()* 所以我们知道如何通知它们。* */@Overridepublic void notifyObservers() {for (int i = 0; i < observers.size();i++){Observer observer = (Observer) observers.get(i);observer.update(tempreture, humidity, pressure);}}/**当气象站检测设备检测到变化时,会通过该方法通知我们气象站* */public void measurementsChanged(){/**当该值发生变化时,主题subject可以在此通知观察者们,*/notifyObservers();}//我们现在只能通过该方法,模拟从检测装置中读取到实际的气象数据,//设置值也就相当于气温等监测装置发现气温发生了变化,同时会告知我们//(事实上,真实的检测装置也就是这么工作的)public void setMeasurements(float tempreture,int humidity,float pressure){this.tempreture = tempreture;this.humidity = humidity;this.pressure = pressure;//气温等发生了变化,通过如下方法告知主题,主题发现气温变化了,//通知所有的观察者们,//你可能会问,为什么该装置不直接跨过主题告诉观察者们,//因为该对象就是气象监测装置监测到数据时发送给我们的measurementsChanged();}}
//公告板,观察者模式中的观察者之一,即可能还有n多个类似的公告板//该公告板一方面要作为天气数据主题的观察者,同时为了展示出来,//需要有方法进行展示出来,告知人类。因为可能有n多个公告板,每个//公告板可能略有差异,但是展示这个方法是必须有的,所以我们将展示//的公用方法抽取出来,作为接口,即,是公告必须具备的功能就是展示private float tempreture;private int humidity;private float pressure;private Subject weatherData;/**构造器需要weatherData对象(也就是主题)作为注册之用*/public  CurrentConditionsDisplay(Subject weatherData){this.weatherData = weatherData;weatherData.registerObserver(this);}//只是把最近的温度展示出来@Overridepublic void show() {System.out.println("Current conditions: "+tempreture+",F degrees and"+humidity+"% humidity");}@Overridepublic void update(float temp, int humidity, float pressure) {//我们把温度和湿度保存起来,然后调用展示数据的方法this.tempreture = temp;this.humidity = humidity;this.pressure = pressure;show();}

为了看出效果,模拟有三块公告板,只有show()方法不一样

package com.example.zhouxueli.myapplication.ObserverDesign;public class ForeCastConditionDisplay implements Observer,DisplayElement{//公告板,观察者模式中的观察者之一,即可能还有n多个类似的公告板//该公告板一方面要作为天气数据主题的观察者,同时为了展示出来,//需要有方法进行展示出来,告知人类。因为可能有n多个公告板,每个//公告板可能略有差异,但是展示这个方法是必须有的,所以我们将展示//的公用方法抽取出来,作为接口,即,是公告必须具备的功能就是展示private float tempreture;private int humidity;private float pressure;private Subject weatherData;/**构造器需要weatherData对象(也就是主题)作为注册之用*/public ForeCastConditionDisplay(Subject weatherData){this.weatherData = weatherData;weatherData.registerObserver(this);}//只是把最近的气压展示出来,这是和另一个公告板不一样的地方@Overridepublic void show() {System.out.println("Current conditions: "+tempreture+",P pressure and"+pressure+"% pressure");}@Overridepublic void update(float temp, int humidity, float pressure) {//我们把温度和湿度保存起来,然后调用展示数据的方法this.tempreture = temp;this.humidity = humidity;this.pressure = pressure;show();}
}
package com.example.zhouxueli.myapplication.ObserverDesign;public class SatisfyConditionDisplay implements Observer,DisplayElement {//公告板,观察者模式中的观察者之一,即可能还有n多个类似的公告板//该公告板一方面要作为天气数据主题的观察者,同时为了展示出来,//需要有方法进行展示出来,告知人类。因为可能有n多个公告板,每个//公告板可能略有差异,但是展示这个方法是必须有的,所以我们将展示//的公用方法抽取出来,作为接口,即,是公告必须具备的功能就是展示private float tempreture;private int humidity;private float pressure;private Subject weatherData;/**构造器需要weatherData对象(也就是主题)作为注册之用*/public  SatisfyConditionDisplay(Subject weatherData){this.weatherData = weatherData;weatherData.registerObserver(this);}//只是把最近的温度展示出来@Overridepublic void show() {if (75 == pressure){System.out.print("now ,it is the satisyfyPressure");}else{System.out.print("please adjust the pressure to satisfy pressure");}}@Overridepublic void update(float temp, int humidity, float pressure) {//我们把温度和湿度保存起来,然后调用展示数据的方法this.tempreture = temp;this.humidity = humidity;this.pressure = pressure;show();}
}

创建将他们一起连接起来的测试类气象站类

package com.example.zhouxueli.myapplication.ObserverDesign;public class WeatherStation {public static void main(String[] args){//气象站需要首先建立一个WeaterDataSubject对象WeatherDataSubject weatherDataSubject=new WeatherDataSubject();//建立三个布告板,将weatherDataSubject对象传给它CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherDataSubject);ForeCastConditionDisplay foreCastConditionDisplay =new ForeCastConditionDisplay(weatherDataSubject);SatisfyConditionDisplay satisfyConditionDisplay=new SatisfyConditionDisplay(weatherDataSubject);//如下为模拟几项气象监测值,每次发生变化,会通知给气象监测对象weatherDataSubject.setMeasurements(80,65,30.4f);weatherDataSubject.setMeasurements(82,70,29.3f);weatherDataSubject.setMeasurements(78,90,28.2f);}
}

如上,完全根据我们的理解,和对Java基本代码的使用进行了创建,在jdk内部,其实有内置的处理该问题的方案,即Observalbe 和 Observer ,后面我将具体代码贴上。

注:

1、收到评论,说本部分代码拷贝测试时发现问题,经本人自测,发现该部分代码直接粘贴时,可能因为引用包错误的问题,导致编译出错。原因是,如上部分是我们自行推导的解决方案,其中的Observer   Subject等接口,在当前jdk中都有实现。所以在使用该接口,需要导入包时,特别注意,要保证导入这几个接口的相应包是我们自定义的包,而非java.util.包 中的Subject 或者Observer。

2、但下部分,即引入jdk自身为了解决该类问题的解决方案。以此处开始区分,前后使用的接口来源注意区分。

二、jdk中的观察者模式体现:

1、Observable 是一个类,而不是一个接口,这点是与上面subject不同的。Observable类追踪所有的观察者,并通知他们。

Observer 接口其实就是上面的observer。

2、那如何把对象变成观察者。。。。

如同以前一样,实现观察者接口(Java.util.Observer),然后调用任何Observer对象的addObserver()方法。不想当观察者时,调用deleteObserver()方法就可以了。

3、可观察者如何送出通知。。。

首先,你需要利用扩展Java.util.Observerable 接口 产生可观察者类,然后,需要两个步骤:

步骤一:先调用setChanged()方法,标记状态已经改变的事实。

步骤二:然后调用两种notifyObservers()方法中的一个:

notifyObservers() 或 notifyObservers(Object  arg)

4、观察者如何接收通知。。。

同以前一样,观察者实现了更新的方法,但是方法的签名不太一样:

update( Observers  o ,Object  arg);  //当通知时,此版本可以传送任何的数据对象给每一个观察者

如果你想推送数据给观察者,你可把数据当做数据对象传送给notifyObservers(Object  arg) 方法。否则,观察者就必须从可观察者中拉数据。

5、实例演示下:

其他方法基本不变,只是将原来的主题和观察者 两个类换一下,测试方法,更改下类名即可。

package com.example.zhouxueli.myapplication.ObserverDesign;import java.util.Observable;public class WeatherData extends Observable {private float tempreture;private int humidity;private float pressure;//我们不再需要追踪观察者了,也不需要管理注册与删除(让超类代劳即可)//所以我们把注册、添加、通知的相关代码删除public WeatherData(){ }public void measurementsChanged(){//setChanged()方法可以让你在更新观察者时,有更多的弹性,你可以更适当地通知//观察者。比如,我们在这个例子中,可以考虑增加温度变化一度时才发出通知,否则//不通知的逻辑,避免温度变化梯度太小,导致通知过于频繁,通信繁忙的问题setChanged();//此处没有使用携带数据包的方法,说明我们采用拉的方法,而不是推的方法//也就是说,后面的getter()方法将更有意义(相对于推送,用户不会调用getter方法来说notifyObservers();}public void setMeasurements(float tempreture,int humidity,float pressure){this.tempreture = tempreture;this.humidity = humidity;this.pressure = pressure;//当检测数据发生变化时,会告诉可观察者对象measurementsChanged();}public float getTempreture() {return tempreture;}public int getHumidity() {return humidity;}public float getPressure() {return pressure;}
}
package com.example.zhouxueli.myapplication.ObserverDesign;import java.util.Observable;
import java.util.Observer;public class Current2ConditionDisplay implements Observer,DisplayElement {private Observable observable;private float tempreture;private int humidity;private float pressure;@Overridepublic void update(Observable o, Object arg) {if (arg instanceof WeatherData){    //首先判断类型是否正确,然后采取拉的方式,获取值WeatherData weatherData = (WeatherData) arg;this.tempreture =((WeatherData) arg).getTempreture();this.humidity = ((WeatherData) arg).getHumidity();this.pressure = ((WeatherData) arg).getPressure();show(); //最后调用展示的方法,该方法只为辅助,让大家更容易接受和理解}}@Overridepublic void show() {System.out.println("now, the tempreture:"+tempreture+",the humidity: "+humidity+",the pressure:"+pressure);}
}

观察者模式之气象监测站实例演示(一)相关推荐

  1. Docker selenium自动化 - 使用python操作docker,python运行、启用、停用和查询容器实例演示

    Docker selenium 自动化 - 使用 Python 操作 docker 运行.启用.停用和查询容器实例演示 第一章:Python 操作 docker ① python 运行 docker ...

  2. Docker selenium自动化 - Python调用容器实例跑自动化查天气实例演示,docker selenium自动化环境部署过程

    Docker selenium自动化 - 环境部署与 Python 自动化运行实战演示 第一章:docker selenium 环境部署 ① 下载 selenium 镜像 ② 容器 selenium ...

  3. jQuery数组处理详解(含实例演示)

    jQuery的数组处理,便捷,功能齐全. 最近的项目中用到的比较多,深感实用,一步到位的封装了很多原生js数组不能企及的功能. 最近时间紧迫,今天抽了些时间回过头来看 jQuery中文文档 中对数组的 ...

  4. 《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)

    一.粘包/拆包概念 TCP是一个"流"协议,所谓流,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的 ...

  5. java 调试 gdb_android gdb 调试实例演示(有源代码篇)

    android ndk代码的调试本身还是有点麻烦的,因为本身google android的sdk 主要是面向广大的java程序员的,所以后来发布的 ADT 集成开发环境对java的代码调试 支持还是很 ...

  6. CSS3文本居中显示、圆形圆角绘制、立体阴影效果设置实例演示

    CSS3文本居中显示.圆形圆角绘制.立体阴影效果设置 实例演示 ① 文本居中显示 ② 圆角设置 ③ 圆形设置 ④ 立体阴影效果设置 [ 推荐文章 ] 一篇文章快速掌握 Linux 基本命令 实例演示 ...

  7. Vue前后台数据交互实例演示,使用axios传递json字符串、数组

    Vue 前后台数据交互实例演示 第一章:后台实现 ① Python 启用 Flask 服务器 ② 后台启用成功验证 第二章:前台实现 ① Vue 使用 Axios 实现接收 json 字符串.数组数据 ...

  8. Python 连接FTP服务器并实现文件夹下载实例演示,python区分ftp目录下文件和文件夹方法,ftp目录下包含中文名问题处理

    Python 连接 FTP 服务器并实现文件夹下载实例演示 第一章:连接 FTP 服务器并实现文件夹下载 ① 连接 FTP 服务器 ② 进入指定目录并显示文件信息 ③ 区分文件和文件夹名 ④ 文件夹名 ...

  9. PyQt5 图形界面 - 配置界面跟随窗口大小调整灵活伸缩,设置页面控件居中显示实例演示

    PyQt5 图形界面 - 配置页面跟随窗口大小调整灵活伸缩 第一章:Qt 窗口布局调整演示 ① 不可自由伸缩实例 ② 分散布局合并 ③ 添加间隔控件 ④ 添加栅格布局 ⑤ 修改栅格布局为 QFrame ...

最新文章

  1. linux中各种文件的颜色表示是什么意思?
  2. JAVA中经过nginx反向代理获取客户端ip并获取相关坐标等信息
  3. ADO.NET——二级联动 +ajax
  4. vim无法保存退出_180万程序员不知如何退出Vim编辑器...
  5. 哥伦比亚大学的材料更新提交窗口!干货!
  6. 搭建sql注入实验环境(基于windows)
  7. android开发版本,Android开发之版本统一规范
  8. 大话TreeMap的put,get过程
  9. html css外接修改无效,HTML外部引用CSS文件为什么会不生效
  10. 向net core 3.0进击——Swagger的改变
  11. 计数原理,递推,求从左边能看到l个棒子,右边能看到r个棒子的方案数目
  12. mysql服务等待应答超时_从mysql备份报错来看net_read_timeout 和net_write_timeout参数
  13. 二叉树线索化示意图_二叉树的线索化
  14. python开发安卓盒子_Python盒子:模块、包和程序
  15. hadoop复合键排序使用方法
  16. Linux安装JDK-8-附有百度网盘链接
  17. 面向AMD64的文件xxx与项目的目标平台x86不兼容
  18. 腾讯云内容分发网络 CDN 产品认证课程笔记(二)——腾讯云CDN介绍
  19. 阴阳师服务器维护6,《阴阳师》手游6月10日维护更新公告
  20. 598. 范围求和 II【我亦无他唯手熟尔】

热门文章

  1. unity 使用rigidbody.addforce()跳跃的正确方法
  2. Java基础系列-Collector和Collectors
  3. AD如何生成Gerber文件,钻孔文件与坐标文件
  4. 入职四个月后,我当了一回面试官,面试了一名二本院校学生,结果。。。。
  5. 2.5d矢量插画|(上)-概述
  6. ZXing3.3.3 生成二维码带logo
  7. 快速制作机房3D效果图教程 1
  8. 弘辽科技:想要做好淘宝店铺,做好店铺定位很重要。
  9. .net core微服务入门之Polly
  10. 17、从1加到100并显示结果