【Head First 设计模式】-- 观察者模式
背景
客户有一个WeatherData对象,负责追踪温度、湿度和气压等数据。现在客户给我们提了个需求,让我们利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。
WeatherData对象提供了4个接口:
getTemperature():获取温度
getHumidity():获取湿度
getPressure():获取气压
measurementsChanged():一旦气象测量更新,此方法会被调用
我们的工作是实现measurementsChanged(),好让它更新目前状况、气象统计、天气预报的显示布告板。
我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。
先看一个错误示范
public class WeatherData{//实例变量声明public void measurementsChanged(){//获取温度、湿度、气压数据float temp=getTemperature();float humidity=getHumidity();float pressure=getPressure(); //调用update()更新布告板currentConditionsDisplay.update(temp,humidity,pressure);statisticsDisplay.update(temp,humidity,pressure);forecastDisplay.update(temp,humidity,pressure);
}//其他WeatherData方法
}
回顾第一章中的概念与原则
currentConditionsDisplay.update(temp,humidity,pressure);
statisticsDisplay.update(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure);
针对具体实现编程会导致以后增删布告版时必须修改程序,
对变化的部分必须封装起来。
认识观察者模式
先看看报纸和杂志的订阅是怎么回事:
报社的业务就是出版报纸;
向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸;
当你不想再看报纸的时候,取消订阅,他们就不会再送新报来;
只要报社还在运营,就会有人向他们订阅报纸或取消订阅报纸。
观察者模式与订阅报纸类似,出版者改称为“主题”(Subject),订阅者改称为“观察者”(Observer)。主题对象管理某些数据。当主题内的数据改变,就会通知观察者。已经订阅了主题的观察者会在主题数据发送改变时收到通知。如果观察者不想接收新的数据,可以取消订阅,之后主题数据改变时就不会收到通知。
定义观察者模式
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变时,他的所有依赖者都会收到通知并自动更新。
类图
主题与观察者之间松耦合
当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。
关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。
任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。
有新类型的观察者出现时,主题的代码不需要修改。我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题或观察者,可以轻易地复用。因为二者并非紧耦合。
改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
设计气象站
思考:我们把WeatherData对象当作主题,把布告板当作观察者,布告板为了取得信息,就必须先向WeatherData对象注册。
我们必须记得,每个布告板都有差异,这也就是为什么我们需要一个共同的接口的原因。尽管布告板的类都不一样,但是它们都应该实现相同的接口,好让WeathcrData对象能够知道如何把观测值送给它们。所以每个布告板都应该有一个大概名为update()的方法,以供WeatherData对象调用,而这个update)方法应该在所有布告板都实现的共同接口里定义。
类图
实现(一)
java为观察者模式提供了内置支持。但是,我们暂时不用它,而是先自己动手。虽然,某些时候可以利用Java内置的支持,但是有许多时候,自己建立这一切会更具弹性(况且建立这一切并不是很麻烦)。
所以,让我们从建立接口开始吧:
Subject接口
public interface Subject {//这两个方法都需要一个观察者作为变量,该观察者是用来注册或者被删除的public void registerObserver(Observer o);public void removeObserver(Observer o);//主题状态改变时,此方法被调用,以通知所有的观察者public void notifyObservers();
}
Observer接口
public interface Observer {//当气象观测值改变时,主题会把这些状态值当作方法参数传递给观察者//所有方法都必须实现update()方法,以实现观察者接口public void update(float temp ,float humidity,float pressure);
}
DisplayElement接口
public interface DisplayElement {//当布告板需要显示时调用此方法public void display();
}
在WeatherData中实现主题接口
public class WeatherData implements Subject {//ArrayList用于记录观察者private ArrayList observers;private float temperature;private float humidity;private float pressure;public WeatherData() {// 在构造方法中建立ArrayList observers=new ArrayList();}@Overridepublic void registerObserver(Observer o) {// 当注册注册观察者时,将它加到ArrayList后面即可observers.add(o);}@Overridepublic void removeObserver(Observer o) {// 当观察者取消订阅时,则将它从Arraylist中删除int i=observers.indexOf(o);if(i>=0)observers.remove(o);}@Overridepublic void notifyObservers() {// 将状态告诉每一个观察者,因为每个观察者都实现了update()方法for(int i=0;i<observers.size;i++){Observer observer=(Observer)observers.get(i);observer.update(temperature, humidity, pressure);}}//当数据更新时,通知观察者public void measurementsChanged() {notifyObservers();}public void setMeasurements(float temperature,float humidity,float presure) {//测试用方法this.temperature=temperature;this.humidity=humidity;this.pressure=presure;measurementsChanged();}
}
布告板建立
此处只展示“目前状况”布告板,另外两个与此类似。
public class CurrentConditionsDisplay implements Observer, DisplayElement {private float temperature;private float humidity;private Subject weatherData;public CurrentConditionsDisplay(Subject weatherData) {this.weatherData=weatherData;weatherData.registerObserver(this);}@Overridepublic void display() {//显示当前温湿度状况System. out. println("Current conditions:"+ temperature+"F degrees and"t humidity +"% humidity");}@Overridepublic void update(float temperature, float humidity, float pressure) {this.temperature=temperature;this.humidity=humidity;display();}
}
测试类
public static void main(String[] args) {// TODO 自动生成的方法存根WeatherData weatherData=new WeatherData();CurrentConditionsDisplay currentiDisplay=new CurrentConditionsDisplay(weatherData);weatherData.setMeasurements(80, 65, 30.4f);weatherData.setMeasurements(82, 75, 29.2f);weatherData.setMeasurements(78, 90, 29.2f);}
输出
这种观察者模式总是在数据改变时自动推送全部数据,而观察者没有主动获取数据的方法,因此有时会让观察者很困扰,总是收到一大堆数据而观察者想要的只是其中一个或两个而已。但让观察者自己去取得数据就必须开放权限,这样又带来数据安全性问题,或者使用getter方法又会让需要很多数据的观察者多次调用才能全部取得想要的数据。
主动推送与观察者自行获取都有各自的优缺点,因此Java内置的Observer模式两种方法都支持!
java.util包(package)内包含最基本的Observer接口与Observable类,这和我们的Subject接口与0bserver接口很相似。
Observer接口与0bscrvable类使用上更方便,因为许多功能都已经事先准备好了。你甚至可以使用推(push)或拉(pull)的方式传送数据。
使用Java内置观察者模式类图
类图大体和原来差不多,主题接口变为了Observerable类,WeatherData也不再提供addObserver()等方法而是继承自Observerable.
如何把对象变成观察者…
如同以前一样,实现观察者接口(java.uitl.Observer),然后调用任何Observable对象的addObserver)方法。不想再当观察者时,调用deleteObserver()方法就可以了。
可观察者要如何送出通知……
首先,你需要利用扩展java.util.Observable接口产生“可观察者”类,然后,需个步骤:
①先调用setChanged()方法,标记状态已经改变的事实。
②然后调用两种notifyObservers0方法中的一个:
notifyobservers()或 notifyobservers(object arg)
观察者如何接收通知……
同以前一样,观察者实现了更新的方法,但是方法的签名不太一样:
如果你想“推”(push)数据给观察者,你可以把数据当作数据对象传送给notifyObservers(arg)方法。否则,观察者就必须从可观察者对象中“拉”(pull)数据。
其中 setChanged()方法是用来标记状态是否改变的,好让notifyObservers()知道当它被调用时应该更新观察者。如果调用notifyObservers)之前没有先调用setChanged(),观察者就“不会”被通知。让我们看看Observable内部,以了解这一切:
这样做有其必要性。setChanged()方法可以让你在更新观察者时,有更多的弹性,你可以更适当地通知观察者。
利用内置的支持重做气象站
首先,把WeatherData改成使用java.util.Observable
import java.util.Observable;//导入Observerable
import java.util.Observer;
public class WeatherData extends Observable {private float temperature;private float humidity;private float pressure;public WeatherData(){}//不再需要为了记住观察者们而建立数据结构了public void measurementsChanged(){setChanged();notifyObservers();//我们没有调用 notifyObsevets()传送数据对象,这表示我们采用的做法是“拉” }public void setMeasurements(float temperature,float humidity,float pressure){this.temperature=temperature;this.humidity=humidity;this.pressure=pressure;measurementsChanged();}public float getTemperature(){return temperature;}public float getHumidity(){return humidity;}public float getPressure(){return pressure;}
}
重做CurrentConditionsDisplay
import java.util.Observable;//导入Observerable
import java.util.Observer;
//实现java.util.Observer接口
public class CurrentConditionsDisplay implements Observer, DisplayElement {Observerable observable;private float temperature;private float humidity;public CurrentConditionsDisplay(Observable observable) {// 将Observer当参数,并将CurrentConditionsDisplay对象登记为观察者this.observable=observable;observable.addObserver(this);}@Overridepublic void display() {//显示当前温湿度状况System. out. println("Current conditions:"+ temperature+"F degrees and"t humidity +"% humidity");}@Overridepublic void update(Observable obs, Object arg) {// TODO 自动生成的方法存根if(obs instanceof WeatherData){WeatherData weatherData=(WeatherData)obs;this.temperature=weatherData.getTemperature();this.humidity=weatherData.getHumidity();display();}}
}
输出
嗯!你注意到差别了吗?再看一次……
你会看到相同的计算结果,但是奇怪的地方在于,文字输出的次序不一样。怎么会这样呢?
思考一下…
不要依赖于观察者被通知的次序
java.uitl.Observable实现了它的notifyObservers()方法,这导致了通知观察者的次序不同于我们先前的次序。谁也没有错,只是双方选择不同的方式实现罢了。但是可以确定的是,如果我们的代码依赖这样的次序,就是错的。为什么呢?因为一旦观察者/可观察者的实现有所改变,通知次序就会改变,很可能就会产生错误的结果。这绝对不是我们所认为的松耦合。
java.util.Observable的弊端
如同你所发现的,可观察者是一个“类”而不是一个“接口”,更糟的是,它甚没有实现一个接口。不幸的是,java.util.Observable的实现有许多问题,限制了它的使用和复用。这并不是说它没有提供有用的功能,只是想提醒大家注意一些事实。
Observable是一个,类到底会造成什么问题
首先,因为Observable是一个“类”,你必须设计一个类继承它。如果某类想同时具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。
这限制了Observable的复用潜力(而增加复用潜力不正是我们使用模式最原始的动机吗?)。
再者,因为没有Observable接口,所以你无法建立自己的实现,和Java内置的Observer API搭配使用,也无法将java.util的实现换成另一套做法的实现(比方说,Observable将关键的方法保护起来如果你看看ObservableAP1,你会发现setChanged)方法被保护起来了(被定义成protected)。那又怎么样呢?这意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来。这个设计违反了第二个设计原则:“多用组合,少用继承”。
如果你能够扩展java.util.Observable,那么Observable“可能”可以符合你的需求。否则,你可能需要像本章开头的做法那样自己实现这一整套观察者模式。
在JDK中,并非只有在java.util中才能找到观察者模式,共实在JavaBeans和Swing中,也都实现了观察者模式。
结束
具体实现的代码,我觉得还是看这本书来的清晰,在本章节末尾,还说到"推模式"和"拉模式",所谓"推模式"就是主题对象传递数据,
"拉模式"就是观察者对象主动得数据,我们要牢记一些设计原则:
- 封装变化
- 多用组合,少用继承
- 为交互对象之间的松耦合设计而努力
相关文章:

【Head First 设计模式】-- 观察者模式
背景 客户有一个WeatherData对象,负责追踪温度、湿度和气压等数据。现在客户给我们提了个需求,让我们利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。 WeatherData对象提供了4个接口: …...

JavaWeb篇_01——JavaEE简介【面试常问】
JavaEE简介 什么是JavaEE JavaEE(Java Enterprise Edition),Java企业版,是一个用于企业级web开发平台,它是一组Specification。最早由Sun公司定制并发布,后由Oracle负责维护。在JavaEE平台规范了在开发企业级web应用…...

QtC++与QRadioButton详解
介绍 QRadioButton 是 Qt 中的一个重要部件,用于创建单选按钮,它有以下几个主要作用和特点: 单选功能: QRadioButton 用于创建单选按钮,用户可以从一组互斥的选项中选择一个。这在用户界面设计中常用于需要用户从多个…...

移远EC600U-CN开发板 day01
1.官方文档快速上手,安装驱动,下载QPYcom QuecPython 快速入门 - QuecPython (quectel.com)https://python.quectel.com/doc/Getting_started/zh/index.html 注意: (1)打开开发板步骤 成功打开之后就可以连接开发板…...

【C/C++】什么是POD(Plain Old Data)类型
2023年11月6日,周一下午 目录 POD类型的定义标量类型POD类型的特点POD类型的例子整数类型:C 风格的结构体:数组:C 风格的字符串:std::array:使用 memcpy 对 POD 类型进行复制把POD类型存储到文件中,并从文…...

注册虾皮买家号需要哪些资料?
注册虾皮买家号其实是很简单的,使用相应国家的手机号及对应的环境就可以注册了的,如果想要账号更方便使用,也可以绑定邮箱进行认证。 而如果想要使用shopee买家通系统进行自动化的注册,那么对于资料就有一定的要求了。 1、手机号…...

小腿筋膜炎怎么治疗最有效
小腿筋膜炎症状主要有疼痛、肌肉紧张、活动受限等。 1.疼痛:小腿筋膜炎主要会导致炎症性疼痛,没有固定的压痛点,通常以踝关节、膝关节活动时疼痛为主。疼痛呈持续性,或者反复发作,尤其是在晨起或者天气变化、劳累、受…...

After Effects 2024 v24.0.2(AE2024)
After Effects 2024是视频特效和动态图形设计软件。以下是After Effects 2024的主要功能和特点: 支持创建各种令人惊叹的视觉效果,例如粒子系统、合成特效、绿屏抠像等。支持动画制作,包括关键帧动画、形状动画、运动跟踪等工具,…...

自己实现一个自动检测网卡状态,并设置ip地址
阅读本文前,请先学习下面几篇文章 《搞懂进程组、会话、控制终端关系,才能明白守护进程干嘛的?》 《简简单单教你如何用C语言列举当前所有网口!》 《Linux下C语言操作网卡的几个代码实例!特别实用》 《安卓如何设置…...

【Linux】进程程序替换
文章目录 替换原理站在进程的角度站在程序的角度初体验及理解原理 替换函数函数解释命名理解exec系列函数与main函数之间的关系在一个程序中调用我们自己写的程序 替换原理 创建子进程的目的是什么? ->想让子进程执行父进程代码的一部分 执行父进程对应的磁盘代码…...

项目构建工具maven的基本配置+idea 中配置 maven
👑 博主简介:知名开发工程师 👣 出没地点:北京 💊 2023年目标:成为一个大佬 ——————————————————————————————————————————— 版权声明:本文为原创文…...

【解密ChatGPT】:从过去到未来,揭示其发展与变革
🎊专栏【ChatGPT】 🌺每日一句:天行健,君子以自强不息,地势坤,君子以厚德载物 ⭐欢迎并且感谢大家指出我的问题 文章目录 一、ChatGPT的发展历程 二、ChatGPT的技术原理 三、ChatGPT的应用场景 四、ChatGPT的未来趋势 五、总结 引言:随着…...

系统架构设计】计算机公共基础知识: 5 数学与经济管理
一 运筹方法 1 线性规划 线性规划问题的数学模型通常由线性目标函数、线性约束条件、变量非负条件组成,特点如下: (1)线性规划的可行解域是由一组线性约束条件形成的。 (2)如果存在两个最优解,则连接这两点的线段内所有的点都是最优解,而线段两端延长线上可能会超出…...

Visual Studio 2019光标变成灰色方块问题
文章目录 Visual Studio 2019光标变成灰色方块问题问题描述解决方案 Visual Studio 2019光标变成灰色方块问题 问题描述 单击和双击都无法选中单词,总是选择整行或者是当前光标处的前几个字符一起选中,没有规则,貌似选择单词复制࿰…...

C++ http协议POST body raw 字段向服务器发送请求
环境:ubuntu系统c使用http协议不是很方便,通过curl库我们可以很方便使用http协议,由于我的请求方式比较特殊,在网上没有找到相关的资料,之前使用python实现过一版,但是当设备数量超过100台时,程…...

通过migrate命令实现两个redis实例之间的数据迁移
本文适用于将源服务器的redis实例的key的数据迁移到其他服务器的redis实例 一、migrate简介: migrate用于在Redis实例间进行数据迁移,实际上migrate命令是将dump、restore、del三个命令进行组合,从而简化了操作流程。migrate命令具有原子性&…...

Unity 判断两个UI是否相交
今天碰到要判断两个UI是否相交的交互。 尝试了下,发现有两个方法都成功了。 1、使用Collider2D组件 分别创建两个Image组件,并且添加Collider2D组件,其中一个还要添加Rigidbody2D组件,如下图: 然后创建个判断脚本“…...

swoole process 消息通信
swoole文档:Swoole 文档 process子进程和父进程之间通信,依靠监听。子进程和父进程分别做监听。父进程写入信息,子进程监听接收。子进程向父进程写入,调用父进程监听。 子进程向父进程写入信息有两种方式,一种调用wr…...

uniapp跳转方式
UniApp 是一个基于 Vue.js 的跨平台开发框架,可以同时构建 iOS、Android、H5 和小程序等多个平台的应用。在 UniApp 中,我们可以使用以下几种方式进行页面之间的跳转: 使用 uni.navigateTo 方法:该方法可以在当前页面打开新的页面…...

六大排序算法:插入、选择、冒泡、快排、希尔、归并
1、插入排序 解析:第一个元素设定为已经排好序,依次选择后续的元素插入到已经排好序的组内进行排序。 图示: 代码: public static void insertionSort(int[] arr) {int n arr.length;for (int i 1; i < n; i) {int key a…...

短信登录实现(黑马点评为例)
文章目录 前言一、隐藏用户敏感信息二、短信验证登录、注册1.流程2.代码3.使用redis优化解决代码 二、登录拦截(校验)1.流程2.代码 总结 前言 短信登录核心知识 首先黑马点评这个短信登录是一伪验证,即后台调用工具类随机生成六位数字。 1.R…...

【uniapp】签名组件,兼容vue2vue3
网上找了个源码改吧改吧,清除了没用的功能和兼容性,基于uniapp开发的 样子 vue2 使用方法,具体的可以根据业务自行修改 <signature ref"signature" width"100%" height"410rpx"></signature>confi…...

初步利用Ansible实现批量服务器自动化管理
1.Ansible介绍 Ansible是一款开源的自动化运维工具, 在2012年由Michael DeHaan创建, 现在由Red Hat维护。Ansible是基于Python开发的,采用YAML语言编写自动化脚本playbook, 可以在Linux、Unix等系统上运行, 通过SSH协议管理节点, 无需在被管理节点安装agent。Ansible以其简单、…...

网络安全和隐私保护技术
一、定义 网络安全和隐私保护技术是指在互联网和其他网络环境中,通过技术手段保护网络系统、网络数据和用户隐私免于受到恶意攻击、非法访问、窃取或滥用。网络安全和隐私保护技术是保护网络安全和用户隐私的重要手段,是保障互联网和其他网络环境正常运…...

保险行业采购管理痛点及解决方案(数智化采购系统)
随着社会发展,个人和企业有了更多的金融保险需求。对于金融保险公司而言,需要在采购合规的基础上,基于数智化能力,让经营变得更加高效和智能。 1、围绕重点领域,业务加速布局。 保险行业结合自身业务经营重点&#x…...

光学仿真 | 仿真推动以人类视觉感知为本的汽车显示设计
如果产品设计无法使终端用户产生共鸣,就不会存在卓越的工程设计。您可以设计一种结构坚固的方向盘,但如果它被放在错误的位置,就无法实现其用于转向的主要目的。 同样,在围绕人类视觉进行设计时,显示器其实无需具备尽…...

判断两个对象是否不相等operator.ne()
【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 判断两个对象是否不相等 operator.ne() 选择题 下列代码执行输出的结果是? import operator print("【执行】operator.ne(8,8)") print(operator.ne(8,8)) print("【执行】…...

2023年云计算发展趋势:生活的智能未来
目录 引言1 智能家居的崭新时代2 无人驾驶的崭新时代3 虚拟现实的扩展与改进4 人工智能的综合应用5 云计算的可持续性结语 引言 时光荏苒,科技的飞速发展已经成为当今社会的标志之一。在这个数字化时代,云计算已经成为推动技术革新和生活方式改变的关键…...

Spring Boot项目中通过 Jasypt 对属性文件中的账号密码进行加密
下面是在Spring Boot项目中对属性文件中的账号密码进行加密的完整步骤,以MySQL的用户名为root,密码为123321为例: 步骤1:引入Jasypt依赖 在项目的pom.xml文件中,添加Jasypt依赖: <dependency><…...

2.3 矩阵消元
一、消元矩阵 消元矩阵执行消元步骤用到的矩阵。从第 i i i 个方程减去 l i j l_{ij} lij 乘第 j j j 个方程(将 x j x_j xj 从第 i i i 行中消去)。我们需要很多个简单的矩阵 E i j E_{ij} Eij,每一个对应一个主对角线下方要消…...