【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();
     }
     @Override
     public void registerObserver(Observer o) {
         // 当注册注册观察者时,将它加到ArrayList后面即可
         observers.add(o);
     }
     @Override
     public void removeObserver(Observer o) {
         // 当观察者取消订阅时,则将它从Arraylist中删除
        int i=observers.indexOf(o);
        if(i>=0)
              observers.remove(o);
     }
     @Override
     public 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);
     }
     @Override
     public void display() {
         //显示当前温湿度状况
         System. out. println("Current conditions:"+ temperature+"F degrees and"t humidity +"% humidity");
     }
     @Override
     public 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);
     }
     @Override
     public void display() {
         //显示当前温湿度状况
         System. out. println("Current conditions:"+ temperature+"F degrees and"t humidity +"% humidity");
     }
     @Override
     public 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.mfbz.cn/a/116773.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络验证码--你到底是爱它还是恨它?

互联网安全防火墙&#xff08;1&#xff09;--网络验证码的科普 1 戏言部分 为了在网络上吸引大家读这个文章&#xff0c;在想标题的时候&#xff0c;也是够了。本来是严肃的科普学术帖&#xff0c;但是却一股强烈的“不转不是中国人&#xff0c;让男孩沉默女孩流泪” 这种…

OpenSSL生成CA证书

基本概念 证书类别 根证书&#xff1a;生成服务端证书&#xff0c;客户端证书的基础。自签名。服务端证书&#xff1a;由根证书签发。配置在服务器上。客户端证书&#xff1a;由根证书签发。配置在浏览器、移动APP等客户端上。 认证方式 单向认证&#xff08;Client鉴权Serv…

《视觉SLAM十四讲》-- 概述与预备知识

文章目录 01 概述与预备知识1.1 SLAM 是什么1.1.1 基本概念1.1.2 视觉 SLAM 框架1.1.3 SLAM 问题的数学表述 1.2 实践&#xff1a;编程基基础1.3 课后习题 01 概述与预备知识 1.1 SLAM 是什么 1.1.1 基本概念 &#xff08;1&#xff09;SLAM 是 Simultaneous Localization a…

第二章 02Java基础-数据类型、标识符、键盘录入

文章目录 前言一、数据类型二、标识符三、键盘录入总结前言 今天我们学习Java基础,数据类型、标识符、键盘录入 一、数据类型 1.数据类型大体上可以分为两类,一类是基本数据类型,另外一类是引用数据类型。今天我们学习基本数据类型。 2.基本数据类型可以分为四类八种,整…

【网络安全技术】公钥密码体制

一、两种基本模型 1.加密模型 A要给B发信息&#xff0c;那就拿B的公钥加密&#xff0c;传给B&#xff0c;B收到后会拿他自己的私钥解密得到明文。 2.认证模型&#xff08;数字签名&#xff09; A用自己的私钥加密&#xff0c;传输之后&#xff0c;别人拿A的公钥解密&#xff…

亚马逊云科技大语言模型下的六大创新应用功能

目录 前言 亚马逊云科技的AI创新应用 ​编辑 Amazon CodeWhisperer Amazon CodeWhisperer产品的优势 更快地完成更多工作 自信地进行编码 增强代码安全性 使用收藏夹工具 自定义 CodeWhisperer 以获得更好的建议 如何使用Amazon CodeWhisperer 步骤 1 步骤 2 具体…

辅助驾驶功能开发-功能规范篇(22)-9-L2级辅助驾驶方案功能规范

1.3.7.2 行人、骑行者(横向)AEB 系统 1.3.7.2.1 状态机 1.3.7.2.2 信号需求列表 同 1.3.2.1.2。 1.3.7.2.3 系统开启关闭 同 1.3.2.1.3。 触发横向 AEB 的目标包括横向运动的行人、骑行者(包括自行车、摩托车、电瓶车和平衡车上的行人)。 1.3.7.2.4 制动预填充 制动系统…

pyusb环境搭建和无法发包问题

pyusb环境搭建和无法发包问题 项目需要对usb设备进行开发调试&#xff0c;选择搭建pyusb环境进行调试测试&#xff0c;这里记录下完整流程和中间解决的一些问题。 我使用的环境是window10 64bit, vscode 1.84.0 , Python 3.11.6 1 安装流程 参考github上的 https://github.…

伪随机序列——m序列及MATLAB仿真

文章目录 前言一、m 序列1、m 序列的产生2、m 序列的性质①、均衡性②、游程分布③、移位相加特性④、自相关函数⑤、功率谱密度⑥、伪噪声特性 二、M 序列1、m 序列的产生2、m 序列的性质 三、MATLAB 中 m 序列1、m 序列生成函数的 MATLAB 代码2、MATLAB 仿真 前言 在通信系统…

Photoshop 2023 v24.7

Photoshop是一款强大的图像编辑软件&#xff0c;被广泛应用于图像处理、图形设计、数字绘画等领域。它提供了丰富的图像编辑功能&#xff0c;可以用于调整图像的色彩、亮度、对比度等&#xff0c;添加特效、滤镜&#xff0c;以及进行复杂的图像合成和修复。 以下是Adobe Photo…

基于动力学模型的机械臂滑膜控制

一、滑模控制设计思路 参考资料&#xff1a;https://zhuanlan.zhihu.com/p/463230163&#xff08;思路理解&#xff09; https://blog.csdn.net/xiaohejiaoyiya/article/details/90271529&#xff08;干扰的处理&#xff09; 滑模控制的思路有两个关键&#xff0c;一个是设计…

一文通透各种注意力:从多头注意力MHA到分组查询注意力GQA、多查询注意力MQA

前言 通过本博客内之前的文章可知&#xff0c;自回归解码的标准做法是缓存序列中先前标记的键(K)和值(V) 对&#xff0c;从而加快注意力计算速度。然而&#xff0c;随着上下文窗口或批量大小的增加&#xff0c;多头注意力 (MHA)模型中与 KV 缓存大小相关的内存成本显着增长 对…

【多线程】Lambda表达式

package org.example;public class TestLambda {public static void main(String[] args) {Like likenew Like();like.lambda();}}//定义一个函数式接口 interface ILike{void lambda(); }//实现类 class Like implements ILike{Overridepublic void lambda() {System.out.prin…

Excel自学三部曲_Part3:Excel工作场景实战(四)

文章目录 四、高级函数与数据连接1. 多窗口操作2. VLOOKUP函数3. XLOOKUP函数4. CSV数据格式 四、高级函数与数据连接 1. 多窗口操作 如何将两张子表数据&#xff08;战区信息、城市信息&#xff09;连接到主表数据&#xff08;成交数据&#xff09;&#xff0c;增加主要数据的…

“一键批量拆分HTML文本,高效整理文件,提升工作效率“

您是否曾经被大量的HTML文本文件困扰&#xff0c;难以找到所需的特定信息&#xff1f;现在&#xff0c;我们向您推荐一款强大的工具&#xff0c;它能够一键拆分HTML文本&#xff0c;让您轻松实现文件整理&#xff0c;提高工作效率&#xff01; 首先&#xff0c;在首助编辑高手…

人工智能基础_机器学习014_BGD批量梯度下降公式更新_进一步推导_SGD随机梯度下降和MBGD小批量梯度下降公式进一步推导---人工智能工作笔记0054

然后我们先来看BGD批量梯度下降,可以看到这里,其实这个公式来源于 梯度下降的公式对吧,其实就是对原始梯度下降公式求偏导以后的梯度下降公式,然后 使用所有样本进行梯度下降得来的,可以看到* 1/n 其实就是求了一个平均数对吧.所有样本的平均数. 然后我们看,我们这里* 1/n那么…

API接口安全设计

简介 HTTP接口是互联网各系统之间对接的重要方式之一&#xff0c;使用HTTP接口开发和调用都很方便&#xff0c;也是被大量采用的方式&#xff0c;它可以让不同系统之间实现数据的交换和共享。 由于HTTP接口开放在互联网上&#xff0c;所以我们就需要有一定的安全措施来保证接口…

C++11 initializer_list 轻量级初始化列表的使用场景(让自定义类可以用初始化列表的形式来实例化对象)

initializer_list 是 C11 中的一个特性&#xff0c;它允许你使用花括号 {} 中的值列表来初始化容器或数组。通常用于初始化标准库容器&#xff0c;比如 std::vector、std::set、std::map 以及数组。 场景一&#xff1a;用初始化列表初始化容器 std::vector<int> arr {…

【深度学习】pytorch——Autograd

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 深度学习专栏链接&#xff1a; http://t.csdnimg.cn/dscW7 pytorch——Autograd Autograd简介requires_grad计算图没有梯度追踪的张量ensor.data 、tensor.detach()非叶子节点的梯度计算图特点总结 利用Autograd实…

scrapy+selenium框架模拟登录

目录 一、cookie和session实现登录原理 二、模拟登录方法-Requests模块Cookie实现登录 三、cookiesession实现登录并获取数据 四、selenium使用基本代码 五、scrapyselenium实现登录 一、cookie和session实现登录原理 cookie:1.网站持久保存在浏览器中的数据2.可以是长期…