【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中,也都实现了观察者模式。

结束
具体实现的代码,我觉得还是看这本书来的清晰,在本章节末尾,还说到"推模式"和"拉模式",所谓"推模式"就是主题对象传递数据,

"拉模式"就是观察者对象主动得数据,我们要牢记一些设计原则:

  • 封装变化
  • 多用组合,少用继承
  • 为交互对象之间的松耦合设计而努力

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/132533.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

从零入门Chrome插件开发

什么是 Chrome 插件 谷歌浏览器在推出时就以其快速、安全和简洁的特点受到了广大用户的欢迎。随着浏览器的不断发展&#xff0c;谷歌为用户提供了插件开发平台&#xff0c;使开发者能够为浏览器添加各种功能和定制化选项。从此&#xff0c;插件成为了提升用户体验和个性化的重…

AI:54-基于深度学习的树木种类识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

318. 最大单词长度乘积

318. 最大单词长度乘积 难度: 中等 来源: 每日一题 2023.11.06 给你一个字符串数组 words &#xff0c;找出并返回 length(words[i]) * length(words[j]) 的最大值&#xff0c;并且这两个单词不含有公共字母。如果不存在这样的两个单词&#xff0c;返回 0 。 示例 1&…

Canvas 梦幻树生长动画

canvas可以制作出非常炫酷的动画&#xff0c;以下是一个梦幻树的示例。 效果图 源代码 <!DOCTYPE> <html> <head> <meta http-equiv"Content-Type" content"text/html; charsetutf-8" /> <title>梦幻数生长动画</title&…

【数据结构】树与二叉树(二):树的表示C语言:树形表示法、嵌套集合表示法、嵌套括号表示法 、凹入表示法

文章目录 5.1 树的基本概念5.1.1 树的定义5.1.2 森林的定义5.1.3 树的术语5.1.4 树的表示1&#xff0e;树形表示法2&#xff0e;嵌套集合表示法结构体创建树主函数 3&#xff0e;嵌套括号表示法结构体创建树嵌套括号表示法主函数 4&#xff0e;凹入表示法结构体创建树凹入表示法…

python调用飞书机器人发送文件

当前飞书webhook机器人还不支持发送文件类型的群消息&#xff0c;可以申请创建一个机器人应用来实现群发送文件消息。 创建机器人后&#xff0c;需要开通一系列权限&#xff0c;然后发布。由管理员审核通过后&#xff0c;才可使用。 包括如下的权限&#xff0c;可以获取群的c…

深度学习服务器(Linux)开发环境搭建教程

当你拿到一台服务器的使用权时&#xff0c;最头疼的莫过于登陆服务区并配置开发环境。本文将从0开始&#xff0c;讲述一台刚申请的服务器远程登陆并配置开发环境的全过程。希望对你有所帮助 1.登陆服务器 打开MobaXterm软件&#xff0c;创建一个新的Session&#xff0c;选择S…

图及谱聚类商圈聚类中的应用

背景 在O2O业务场景中&#xff0c;有商圈的概念&#xff0c;商圈是业务运营的单元&#xff0c;有对应的商户BD负责人以及配送运力负责任。这些商圈通常是一定地理围栏构成的区域&#xff0c;区域内包括商户和用户&#xff0c;商圈和商圈之间就通常以道路、河流等围栏进行分隔。…

MySQL EXPLAIN查看执行计划

MySQL 执⾏计划是 MySQL 查询优化器分析 SQL 查询时⽣成的⼀份详细计划&#xff0c;包括表如何连 接、是否⾛索引、表扫描⾏数等。通过这份执⾏计划&#xff0c;我们可以分析这条 SQL 查询中存在的 问题&#xff08;如是否出现全表扫描&#xff09;&#xff0c;从⽽进⾏针对优化…

双十一运动健身好物推荐,这几款健身好物一定不要错过!

双十一购物狂欢节又要到了&#xff0c;又要到买买买的时候了&#xff01;相信有很多想健身的小白还在发愁不知道买啥装备&#xff1f;别急&#xff0c;三年健身达人这就给你们分享我的年度健身好物&#xff01; 第一款&#xff1a;南卡Runner Pro4s骨传导耳机 推荐理由&#…

VSCode 连接不上 debian 的问题

之前一台笔记本上安装了 debian12&#xff0c;当时用 vscode 是可以连接上的&#xff0c;但今天连接突然就失败了&#xff0c;失败信息是这样的&#xff1a; 查看失败信息 因为 debian 是自动获取 ip 地址的&#xff0c;以前能连接上时&#xff0c;ip 地址是 104&#xff0c;然…

红队专题-新型webshell的研究

新型webshell的研究 招募六边形战士队员webshell与MemoryShell内存马新型一句话木马之Java篇 AES加密Class二进制解析友军防护为什么会被拦截SO waf防护规则END 一劳永逸绕过waf实现篇服务端实现 前言&#xff1a;你马没了利用JavaAgent技术发现并清除系统中的内存马介绍安全行…

centos7安装nginx-阿里云服务器

1.背景 2.准备工作步骤 2.1.安装gcc 阿里云服务器一般默认是安装了的 检查是否已安装 gcc -v 出现如下信息表示已安装: 如果没有安装,执行 yum -y install gcc 2.2.安装pcre,pcre-devel yum install -y pcre pcre-devel 2.3.安装zlib yum install -y zlib zlib-devel…

PS Raw中文增效工具Camera Raw 16

Camera Raw 16 for mac&#xff08;PS Raw增效工具&#xff09;的功能特色包括强大的图像调整工具。例如&#xff0c;它提供白平衡、曝光、对比度、饱和度等调整选项&#xff0c;帮助用户优化图像的色彩和细节。此外&#xff0c;Camera Raw 16的界面简洁易用&#xff0c;用户可…

Python + Selenium,分分钟搭建 Web 自动化测试框架!

在程序员的世界中&#xff0c;一切重复性的工作&#xff0c;都应该通过程序自动执行。「自动化测试」就是一个最好的例子。 随着互联网应用开发周期越来越短&#xff0c;迭代速度越来越快&#xff0c;只会点点点&#xff0c;不懂开发的手工测试&#xff0c;已经无法满足如今的…

【小白专用】PHP中的JSON转换操作指南 23.11.06

一、JSON的基础知识 1.1JSON数据格式 JSON数据格式是一组键值对的集合&#xff0c;通过逗号分隔。键值对由“键”和“值”组成&#xff0c;中间使用冒号分隔。JSON数据格式可以嵌套&#xff0c;而且可以使用数组 二、PHP中的JSON函数 JSON的操作需要使用编程语言进行处理&am…

.NET Core 中插件式开发实现

在 .NET Framework 中&#xff0c;通过AppDomain实现动态加载和卸载程序集的效果&#xff1b;但是.NET Core 仅支持单个默认应用域&#xff0c;那么在.NET Core中如何实现【插件式】开发呢&#xff1f; 一、.NET Core 中 AssemblyLoadContext的使用 1、AssemblyLoadContext简…

Javaweb之HTML,CSS的详细解析

2.4 表格标签 场景&#xff1a;在网页中以表格&#xff08;行、列&#xff09;形式整齐展示数据&#xff0c;我们在一些管理类的系统中&#xff0c;会看到数据通常都是以表格的形式呈现出来的&#xff0c;比如&#xff1a;班级表、学生表、课程表、成绩表等等。 标签&#xff…

输电线路AR可视化巡检降低作业风险

随着现代工业的快速发展&#xff0c;各行业的一线技术工人要处理的问题越来越复杂&#xff0c;一些工作中棘手的问题迫切需要远端专家的协同处理。但远端专家赶来现场往往面临着专家差旅成本高、设备停机损失大、专业支持滞后、突发故障无法立即解决等痛点。传统的远程协助似乎…

OFDM同步--载波频率偏差CFO

参考书籍&#xff1a;《MIMO-OFDM无线通信技术及MATLAB实现》 实验图基本都截取自该本书 一、什么是CFO OFDM解调是采用同步检波的方式&#xff0c;需要在接收机使用与发射机相同的载波信号进行向下变换恢复出基带信号。但在实际使用中无法获得完全相同的载波信号&#xff0c;…