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

2023-11-03

设计模式入门

本系列所有内容参考自《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. 针对接口编程,不针对实现编程。
    主题与观察者都是用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计模式入门(二)观察者模式 的相关文章

  • 如何从当前 .NET 表单/应用程序发送密钥 F12

    我非常确定以下按钮激活的表单代码应该在我的 C 应用程序中引发 Control F12 SendKeys F12 但它似乎并没有继续进入 Windows shell 并激活另一个正在侦听它的程序 我的键盘可以用 看起来发送键在某处被拦截 并
  • 如何调试参数化 SQL 查询

    我使用 C 连接到数据库 然后使用 Ad hoc SQL 来获取数据 这个简单的 SQL 查询非常方便调试 因为我可以记录 SQL 查询字符串 如果我使用参数化 SQL 查询命令 有没有办法记录 sql 查询字符串以进行调试 我想就是这样的
  • 将 2D 数组映射到 1D 数组

    我想用一维数组来表示一个二维数组 函数将传递两个索引 x y 和要存储的值 这两个索引代表一维数组的单个元素 并相应地设置它 我知道一维数组需要具有 arrayWidth arrayHeight 的大小 但我不知道如何设置每个元素 例如 如
  • 测试 hdf5/c++ 中的组是否存在

    我正在打开一个现有的 HDF5 文件来附加数据 我想向那个叫做的小组保证 A存在以供后续访问 我正在寻找一种简单的方法来创建 A有条件地 如果不存在则创建并返回新组 或者返回现有组 一种方法是测试 A存在 我怎样才能高效地做到这一点 根据
  • 实体框架代码优先 - 在另一个文件中配置

    使用 Fluent API 将表到实体的映射分开的最佳方法是什么 以便它全部位于单独的类中 而不是内联在 OnModelCreating 方法中 我目前在做什么 public class FooContext DbContext prote
  • .NET 可移植类库中的 .ToShortDateString 发生了什么

    我想知道为什么没有 ToShortDateString在 NET 可移植类库中 我有 2 个项目 Silverlight 和常规 NET 类库 使用相同的代码 并且代码涉及调用 ToShortDateString on a DateTime
  • 浮点提升:stroustrup vs 编译器 - 谁是对的?

    在 Stroustrup 的新书 C 编程语言 第四版 第 10 5 1 节中 他说 在执行算术运算之前 整数提升用于从较短的整数类型创建整数 类似地 浮点提升是用于从浮点数创建双精度数 我用以下代码确认了第一个声明 include
  • 控制台应用程序 .net Core 2.0 的配置

    在 net Core 1 中我们可以这样做 IConfiguration config new ConfigurationBuilder AddJsonFile appsettings json true true Build 这样就可以使
  • 如何避免选择项目时 winforms 树视图图标发生变化

    我正在一个小型 C Winforms 应用程序中尝试树视图 我已经以编程方式将 ImageList 分配给树视图 并且所有节点都很好地显示了它们的图标 but当我单击一个节点时 它的图标会发生变化 变为 ImageList 中的第一个图像
  • for 循环 - 没有效果的语句

    由于某种原因 我收到错误 statement with no effect关于这个声明 for j idx j lt iter j increment printf from loop idx i int idx punc ctxt j 你
  • 在 C# 中赋值后如何保留有关对象的信息?

    我一直在问我的想法可能是解决方案 https stackoverflow com questions 35254467 is it possible in c sharp to get the attributes attached to
  • 在可观察项目生成时对其进行处理

    我有一个IObservable它会生成一次性物品 并且在其生命周期内可能会生成无限数量的物品 因此 我想在每次生成新项目时处理最后一个项目 因此Using http reactivex io documentation operators
  • 如何使用 Clang 查找内存泄漏

    我在我的机器 ubuntu 中安装了 Clang 以便发现我的 C 代码中的内存泄漏 我编写了一个示例代码来检查它的工作情况 如下所示 File hello c for leak detection include
  • C# ToString("MM/dd/yy") 删除前导 0 [重复]

    这个问题在这里已经有答案了 可能的重复 格式化 NET DateTime Day 不带前导零 https stackoverflow com questions 988353 format net datetime day with no
  • 改进C++逐行读取文件的能力?

    我正在解析大约 500GB 的日志文件 我的 C 版本需要 3 5 分钟 我的 Go 版本需要 1 2 分钟 我正在使用 C 的流来流式传输文件的每一行以进行解析 include
  • 宏观评价[重复]

    这个问题在这里已经有答案了 可能的重复 未定义的行为和序列点 https stackoverflow com questions 4176328 undefined behavior and sequence points 我无法理解以下宏
  • 多个同名内存数据库

    关系到这个答案 https stackoverflow com a 48446491 596758 我试图通过设置让多个上下文工作UseInMemoryDatabase以同名 下面的测试失败 第二个上下文为空 我还需要做什么才能在内存数据库
  • 局部静态变量初始化是线程安全的[重复]

    这个问题在这里已经有答案了 假设我有一个包含三个静态函数的类 如下所示 include
  • 如何仅更改 DateTime 的日期部分,同时保留时间部分?

    我在代码中使用了很多 DateTime 我想将这些日期时间更改为我的特定日期并保留 时间 1 2012 02 02 06 00 00 gt 2015 12 12 06 00 00 2 2013 02 02 12 00 00 gt 2015
  • Windows 上 libcurl 的静态库[重复]

    这个问题在这里已经有答案了 如何将此库 libcurl 静态链接到 exe 我努力了 disable share enable static 没有帮助 我使用的是MingW32 有没有一种简单的方法来静态链接这个库 这样我的应用程序就不再有

随机推荐

  • python自动化办公——读取PPT写入word表格

    Python自动化办公 读取PPT内容写入word表格 文章目录 Python自动化办公 读取PPT内容写入word表格 一 需求分析 二 导入依赖 三 代码 四 结果及总结 一 需求分析 由于我们知识图谱课程需要将课堂小组汇报的PPT总结
  • Scala与Java混编译:java日志不打印的问题

    1 背景 我本地测试 大部分代码是scla开发 少部分是java代码 然后本地测试都是正确的 19 09 04 20 01 32 INFO TopoSparkSubmitter 加载Spark默认配置文件 Some etc spark2 c
  • 二进制简单计算

    二进制简单计算 1 24 35 值 用二进制补码方式进行计算 24的补码 00011000 35的原码 10100011 35的反码 11011100 35的补码 11011101 24 35的值 00011000 11011101 111
  • R语言中if语句使用方法之超详细教程

    在R语言中 if属于一种分支结构 即根据某个条件执行相关的语句 R中的if语句与else配合主要有3种结构 单个if语句 if cond expr 其它语句 即当括弧中的cond条件为TRUE时 则执行表达式expr 否则跳过后执行其后的语
  • 复习git的使用

    文章目录 复习git的使用 基础 提交文件 查看 回退 撤销修改 分支 创建 切换 tag 其他命令 HEAD 指针 的理解 复习git的使用 最近公司的老旧项目要由svn转到git git 命令大都忘记了 这里复习总结一下 基础 查看本地
  • unity虚拟相机cinemachine 之ScriptingExample源码解读轻松理解其作用

    我从demo里面找到了脚本的源码 运行的效果 是5秒切换到这个cube立方体 又5秒切换到另外一个 cylinder public class ScriptingExample MonoBehaviour CinemachineVirtua
  • 【TVM 学习资料】使用 Python 接口(AutoTVM)编译和优化模型

    本篇文章译自英文文档 Compiling and Optimizing a Model with the Python Interface AutoTVM 作者是 Chris Hoge 更多 TVM 中文文档可访问 TVM 中文站 TVMC
  • 14.navigator.userAgent属性检查浏览器类型

    如何使用navigator userAgent属性检查浏览器类型 navigator userAgent属性是什么 是个只读的字符串 声明浏览器用于HTTP请求的用户代理头的值 如何检查 let a navigator userAgent
  • SQLyog中文乱码的解决方案(中文显示成问号)

    问题描述 在SQLyog中键入的中文都变成了 如下图所示 解决方案 找到乱码的字段 右击然后选择 管理字段 在弹出的页面里点击 隐藏语言选项 取消隐藏 然后就可以看到Charset列 如下图所示 更改Charset列 选择utf8 之后点击
  • ld: warning: object file (/path/WYDemo.framework/WYDemo(WYSingleton.o)) was built for newer iOS vers...

    1 出现场景 1 在制作 WYDemo framework 工程中的 Development target 为 11 2 2 在使用 WYDemo framework 工程中的 Development target 为 8 0 2 解决方案
  • Scrach基本概念与操作

    基本概念 一个程序最初的触发是由事件 黄色积木 负责的 例如点击播放事件 按下空格事件 当接收到消息等 程序由舞台和角色组成 舞台和角色都可以有多个 Scratch本身提供了许多舞台和角色的素材 可直接使用 每个角色都有自己的脚本代码 由各
  • Using join buffer (Batched Key Access)

    2019独角兽企业重金招聘Python工程师标准 gt gt gt Using join buffer Batched Key Access 表连接算法 Batched Key Access BKA 原理 MySQL 5 6版本提供了很多性
  • 利用循环输出文字

    首先设置一个循环的函数 var arr 1 var i 0 function xh i arr var arr 1 var i 0 if i gt arr false else document write 人类的本质是复读机 xh if
  • Windows 安装yolo v4时 Cmake无法检测到CUDA的问题

    最近因为装yolov4真的是头发掉了一大把 好不容易避开了众多坑之后 结果Cmake检测不到CUDA了 具体的安装步骤参照了以下文章 https blog csdn net shuaijieer article details 106150
  • GTest源码剖析(六)——RUN_ALL_TESTS

    GTest源码剖析 RUN ALL TESTS GTest源码剖析RUN ALL TESTS RUN ALL TESTS源码分析 1 UnitTestRun 2 HandleExceptionsInMethodIfSupported 3 U
  • 【华为OD机试真题 JAVA】检查是否存在满足条件的数字组合

    JS版 华为OD机试真题 JS 检查是否存在满足条件的数字组合 标题 检查是否存在满足条件的数字组合 时间限制 1秒 内存限制 262144K 语言限制 不限 给定一个正整数数组 检查数组中是否存在满足规则的数字组合 规则 A B 2C 输
  • 刷入magisk无限重启_Magisk的安装与使用

    随着安卓版本的升级 SuperSU和Xposed的用户越来越少 人们需要一个替代者 于是Magisk出现在大家的视野 本文将对Magisk的安装和使用进行介绍 01 如何安装Magisk首先下载一个Magisk Manager 地址http
  • Linux之执行一个可执行文件

    Linux中执行一个可执行文件 在Linux系统中执行一个可执行文件 只需写正确文件路径 即可执行文件 不需要写命令 1 如果执行当前路径下的文件 文件名 2 执行非当前目录下的文件 文件的绝对路径 注意 以上操作的前提条件 文件是可执行文
  • 想从事区块链开发? 你应该这么做

    凭借每年15 4万美元的平均工资和稳定的就业增长 现在是学习区块链开发的理想时机 为了创建和改进区块链技术 区块链开发人员练习各种技能 包括计算机网络 密码学 算法和数据结构 这些开发人员负责设计以特定业务模型为中心的区块链技术 然后构建
  • 设计模式入门(二)观察者模式

    设计模式入门 本系列所有内容参考自 HeadFirst设计模式 因为书中的代码是采用java语言写的 博主这里用C 语言改写 这里采用讲故事的方式进行讲解 若有错误之处 非常欢迎大家指导 设计模式 模式不是代码 而针对设计问题的通用解决方案