设计模式入门(二)观察者模式

设计模式入门

本系列所有内容参考自《HeadFirst设计模式》。因为书中的代码是采用java语言写的,博主这里用C++语言改写。
这里采用讲故事的方式进行讲解。若有错误之处,非常欢迎大家指导。
设计模式:模式不是代码,而针对设计问题的通用解决方案,被认为是历经验证的OO设计经验。设计模式告诉我们如何组织类和对象以解决某种问题。
如果你输出一个helloworld都想使用设计模式的话,那可能真的就有问题了。

正文

提出问题

我们现在手头有一个气象检测应用。气象站接收湿度感应装置温度感应装置气压感应装置的数据,然后我们有一个WeatherData对象,它负责追踪来自气象站的数据,并更新布告板(显示目前天气状况给用户看)。
图片来自HeadFirst设计模式
如果我们要接手这个项目,我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。三个布告板如下图所示:
在这里插入图片描述

现有的WeatherData类源码如下:

class WeatherData {float getTemperature();  //返回温度float getHumidity();     //返回湿度float getPressure();     //返回气压void measurementsChanged(){/*一旦气象测量更新,此方法会被调用*///我们的代码加在这里}
};

我们的工作是实现measurementsChanged(),好让它更新目前状况、气象统计、天气预报的显示布告板。

我们目前知道的:WeatherData类有三个方法,可以取得三个测量值;当新的数据来临时,measurementsChanged()方法就会被调用(我们不在乎此方法是如何被调用的,我们只在乎它被调用了);我们需要实现三个使用天气数据的布告板,一旦WeatherData有新的测量,这些布告必须马上更新。

一个我们可能想到的measurementsChanged()实现如下:

class WeatherData {// 实例变量声明void measurementsChanged(){// 获取最新的测量值float temp = getTemperature();float humidity = getHumidity();float pressure = getPressure();// 调用每个布告板更新显示currtenConditionsDisplay.update(temp, humidity, pressure);  // 目前状况布告板更新statisticsDisplay.update(temp, humidity, pressure);  // 气象统计布告板更新forecastDisplay.update(temp, humidity, pressure);   // 天气预报布告板更新}
};

但是这与一些软件设计原则发生了矛盾。上面代码中调用每个布告板更新显示函数是针对具体实现编程,会导致我们以后在增加或删除布告板时必须修改程序;三个接口都是update,传入的参数也是一样的,所以看起来更像是一个统一的接口。

那我们该如何解决这个问题呢?观察者模式可以帮助我们很好地解决这个问题。

观察者模式

一个很简单的例子就是杂志订阅
假设我们订阅了一款杂志,每当这款杂志更新时,它都会给我们送一份。这就是观察者模式,杂志相当于“主题”,我们相当于“观察者”,当主题发生改变时,就是通知“观察者”。这里要注意的一点是:主题来增加或删除观察者。 还是杂志订阅这个问题,我们想订阅杂志的时候,杂志出版社便会将我们加到它们的订阅名单里,我们不想订阅杂志时,杂志出版社便会将我们从订阅名单里删除。

观察者模式:观察者模式定义了对象之间的一对多依赖(“一个主题”对“多个观察者”),这样一来,当一个对象改变状态时,它的所有依赖者(因为主题是真正拥有数据的人,观察者是主题的依赖者)都会收到通知并自动更新。

实现代码如下:

#include<iostream>
#include<vector>using namespace std;class Observer {  // 观察者
public:virtual void update(float temp, float humidity, float pressure) = 0;
};class Subject {  // 抽象主题virtual void registerObserver(Observer *o)=0;virtual void removeObserver(Observer *o)=0;virtual void notifyObserver()=0;
};class DisplayElement {virtual void display()=0;
};class WeatherData : public Subject  // 具象主题
{
private:vector<Observer*> observers;float temperature;float humidity;float pressure;
public:void registerObserver(Observer *o)  // 注册观察者{observers.push_back(o);}void removeObserver(Observer *o)   // 取消观察者{auto it = std::find(observers.begin(), observers.end(), o);if (it != observers.end()){int index = std::distance(observers.begin(), it);cout << "索引是:" << index << endl;;observers.erase(observers.begin() + index);cout << "成功删除元素" << endl;}else{cout << "未找到元素" << endl;}}void notifyObserver()  // 通知观察者{for (int i = 0; i < observers.size(); i++){Observer *observer = observers[i];observer->update(temperature, humidity, pressure);}}void measurementsChanged(){notifyObserver();  // 通知观察者}void setMeasurements(float temperature, float humidity, float pressure){this->temperature = temperature; this->humidity = humidity;this->pressure = pressure;measurementsChanged();}
};class StatisticsDisplay : public Observer, public DisplayElement  // 观察者
{
private:float temperature;float humidity;WeatherData *weatherData;
public:StatisticsDisplay(WeatherData *weather){weatherData = weather;weatherData->registerObserver(this);  //主题注册观察者}void remove(){weatherData->removeObserver(this); // 主题取消观察者}void update(float temperature, float humidity, float pressure){this->temperature = temperature;this->humidity = humidity;display();}void display(){cout << "statisticsDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;}
};class ForecastDisplay : public Observer, public DisplayElement  // 观察者
{
private:float temperature;float humidity;WeatherData *weatherData;
public:ForecastDisplay(WeatherData *weather){weatherData = weather;weatherData->registerObserver(this);  //主题注册观察者}void remove(){weatherData->removeObserver(this); // 主题取消观察者}void update(float temperature, float humidity, float pressure){this->temperature = temperature;this->humidity = humidity;display();}void display(){cout << "ForecastDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;}
};class CurrentConditionsDisplay : public Observer, public DisplayElement  // 观察者
{
private:float temperature;float humidity;WeatherData *weatherData;
public:CurrentConditionsDisplay(WeatherData *weather){weatherData = weather;weatherData->registerObserver(this);  //主题注册观察者}void remove(){weatherData->removeObserver(this); // 主题取消观察者}void update(float temperature, float humidity, float pressure){this->temperature = temperature;this->humidity = humidity;display();}void display(){cout << "CurrentConditionsDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;}
};
int main()
{WeatherData *weatherData = new WeatherData; // 定义一个主题对象即可CurrentConditionsDisplay currentDisplay(weatherData);  // 第一个观察者StatisticsDisplay statisDisplay(weatherData);   // 第二个观察者ForecastDisplay foreDisplay(weatherData);   // 第三个观察者weatherData->setMeasurements(80, 65, 30.4);   // 主题信息发生变更currentDisplay.remove();   // 该观察者取消对主题的订阅weatherData->setMeasurements(40, 25, 15.4);foreDisplay.remove();   // 该观察者取消对主题的订阅weatherData->setMeasurements(15.5, 26, 34);return 0;
}

以上就是使用C++实现观察者模式的全部代码。

设计原则

  1. 找出程序中会变化的方面,然后将其和固定不变的方面相分离。
    在观察中模式中,会改变的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。
  2. 针对接口编程,不针对实现编程。
    主题与观察者都是用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。

观察者模式较为重要,在很多软件框架和软件设计中都可以看到它的身影,所以大家可以根据代码仔细体会它的思想。工作的那几个月在公司的软件里看到过观察者模式,但是没有自己动手实现,只是明白它的意思。今天自己动手实现了一下,感悟又深了一些。

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

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

相关文章

SpringMVC-学习笔记

文章目录 1.概述1.1 SpringMVC快速入门 2. 请求2.1 加载控制2.2 请求的映射路径2.3 get和post请求发送2.4 五种请求参数种类2.5 传递JSON数据2.6 日期类型参数传递 3.响应3.1 响应格式 4.REST风格4.1 介绍4.2 RESTful快速入门4.3 简化操作 1.概述 SpringMVC是一个基于Java的Web…

七、性能测试之内存分析

性能测试之内存分析与实战 一、内存知识1、理解&#xff1a;2、内存的组成&#xff1a;内存地址、存储单元3、内存---树形结构1、链表2、二叉树 4、数据结构 二、内存使用1、典型案例&#xff1a;JVM&#xff08;java虚拟机&#xff09;包含程序计数器&#xff0c;java虚拟机栈…

说说你了解的 CDC

分析&回答 什么是 CDC CDC,Change Data Capture,变更数据获取的简称&#xff0c;使用CDC我们可以从数据库中获取已提交的更改并将这些更改发送到下游&#xff0c;供下游使用。这些变更可以包括INSERT,DELETE,UPDATE等。用户可以在以下的场景下使用CDC&#xff1a; 使用f…

燃气管网监测系统,提升城市燃气安全防控能力

燃气是我们日常生活中不可或缺的能源&#xff0c;但其具有易燃易爆特性&#xff0c;燃气安全使用、泄漏监测尤为重要。当前全国燃气安全事故仍呈现多发频发态势&#xff0c;从公共安全的视角来看&#xff0c;燃气已成为城市安全的重大隐忧&#xff01;因此&#xff0c;建立一个…

JVM内存模型

文章目录 一、前言二、JVM内存模型1、Java堆2、方法区3、Java栈3.1、局部变量表3.2、操作数栈3.3、动态链接3.4、返回地址 4、本地方法栈5、程序计数器 一、前言 本文将详细介绍JVM内存模型&#xff0c;JVM定义了若干个程序执行期间使用的数据区域。这个区域里的一些数据在JVM…

Python 类和对象

类的创建 Python语言中&#xff0c;使用class关键字来创建类&#xff0c;其创建方式如下&#xff1a; class ClassName(bases):# class documentation string 类文档字符串&#xff0c;对类进行解释说明class_suiteclass是关键字&#xff0c;bases是要继承的父类&#xff0c;…

李宏毅机器学习笔记:RNN循环神经网络

RNN 一、RNN1、场景引入2、如何将一个单词表示成一个向量3种典型的RNN网络结构 二、LSTMLSTM和普通NN、RNN区别 三、 LSTM的训练 一、RNN 1、场景引入 例如情景补充的情况&#xff0c;根据词汇预测该词汇所属的类别。这个时候的Taipi则属于目的地。但是&#xff0c;在订票系统…

WEBGL(2):绘制单个点

代码如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevi…

Java单元测试及常用语句 | 京东物流技术团队

1 前言 编写Java单元测试用例&#xff0c;即把一段复杂的代码拆解成一系列简单的单元测试用例&#xff0c;并且无需启动服务&#xff0c;在短时间内测试代码中的处理逻辑。写好Java单元测试用例&#xff0c;其实就是把“复杂问题简单化&#xff0c;建单问题深入化“。在编写的…

英国选校8.27|8.29

目录 IC帝国理工学院 UCL伦敦大学学院​​​​​​​ Band A B C 专业院系 爱丁堡 曼彻斯特 KCL伦敦国王学院 Bristol布里斯托 华威 南安普顿 IC帝国理工学院 UCL伦敦大学学院 24qs专业位置双非雅思气候备注9 MSc Scientific and Data Intensive Computing MSc Ur…

在k8s中使用secret存储敏感数据与四种用法

当需要存储敏感数据时可以使用&#xff0c;secret会以密文的方式存储数据。 创建secret的四种方法 &#xff08;1&#xff09;通过--from-literal #每个--from-literal对应一个信息条目 kubectl create secret generic mysecret --from-literalusernameadmin --from-litera…

Spring Boot 中 Nacos 配置中心使用实战

官方参考文档 https://nacos.io/zh-cn/docs/quick-start-spring-boot.html 本人实践 1、新建一个spring boot项目 我的spirngboot版本为2.5.6 2、添加一下依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-…

无涯教程-JavaScript - CUBEMEMBERPROPERTY函数

描述 CUBEMEMBERPROPERTY函数从多维数据集返回成员属性的值。使用此函数可以验证多维数据集中是否存在成员名称,并返回该成员的指定属性。 语法 CUBEMEMBERPROPERTY (connection, member_expression, property)争论 Argument描述Required/OptionalconnectionName of the co…

JavaScript基础语法03——JS注释、结束符

哈喽&#xff0c;大家好&#xff0c;我是雷工&#xff01; 今天继续学习JavaScript基础语法知识&#xff0c;注释和结束符&#xff0c;以下为学习笔记。 一、JavaScript注释 JavaScript注释有什么作用&#xff1f; JavaScript注释可以提高代码的可读性&#xff0c;能够帮助像…

arduino仿真 SimulIDE1.0仿真器

SimulIDE 是一个开源的电子电路模拟器&#xff0c;支持模拟各种电子元器件的行为&#xff0c;可以帮助电子工程师和爱好者进行电路设计和测试。以下是 SimulIDE 的安装和使用说明&#xff1a; 安装 SimulIDE SimulIDE 可以在 Windows、Linux 和 Mac OS X 等操作系统上安装。您…

零知识证明(zk-SNARK)(二)

From Computational Problem to zk-SNARK 本部分就是将计算难题转换为多项式&#xff0c;然后使用zk-SNARK。 &#xff08;注&#xff1a;以下用 P&#xff0c;V 替代 Prover&#xff0c;Verifier&#xff09; 计算难题->R1CS R1CS(Rank-1 Constraint System)是一种能够…

jvm的内存区域

JVM 内存分为线程私有区和线程共享区&#xff0c;其中方法区和堆是线程共享区&#xff0c;虚拟机栈、本地方法栈和程序计数器是线程隔离的数据区。 1&#xff09;程序计数器 程序计数器&#xff08;Program Counter Register&#xff09;也被称为 PC 寄存器&#xff0c;是一块…

基于RabbitMQ的模拟消息队列之二---创建项目及核心类

一、创建项目 创建一个SpringBoot项目&#xff0c;环境&#xff1a;JDK8&#xff0c;添加依赖&#xff1a;Spring Web、MyBatis FrameWork(最主要&#xff09; 二、创建核心类 1.项目分层 2.核心类 在mqserver包中添加一个包&#xff0c;名字为core&#xff0c;表示核心类…

MIPI D-PHY的初始化(MIPI Alliance Xilinx)

DPHY的基本介绍及使用已有很多文章&#xff0c;基本是基于《MIPI Alliance Specification for D-PHY 》的内容&#xff0c;学习时也以此为准&#xff0c;可参考CSDN上的文章。着重讲述MIPI D-PHY的初始化部分 1 D-PHY的功能及使用 下面的文章讲的不错&#xff0c;既有理论&…

iOS swift5 扫描二维码

文章目录 1.生成二维码图片2.扫描二维码&#xff08;含上下扫描动画&#xff09;2.1 记得在info.plist中添加相机权限描述 1.生成二维码图片 import UIKit import CoreImagefunc generateQRCode(from string: String) -> UIImage? {let data string.data(using: String.En…