【C++ | 设计模式】观察者模式的详解与实现

2024-09-02 03:36

本文主要是介绍【C++ | 设计模式】观察者模式的详解与实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.概念

观察者模式(Observer Pattern)是一种行为型设计模式,它的核心思想是定义对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会收到通知并自动更新。这个模式在现实生活中非常常见,比如新闻订阅、社交媒体的推送通知等。

举个简单的例子想象一下,你订阅了一个YouTube频道。当这个频道发布新视频时,你就会收到通知。这就是观察者模式的一个实际应用。YouTube频道相当于“被观察者”,你作为订阅者则是“观察者”。

2. 模式结构

UML结构图:

6c93ad72c2884daba9f1ec5bfb0cab29.png

  • Subject(被观察者/主题):持有对观察者的引用,可以增加、删除、通知观察者。当它的状态发生变化时,会主动通知所有的观察者。
  • Observer(观察者):定义一个接口,用于接收被观察者的通知并更新自身。当被观察者的状态变化时,观察者会接收到通知并执行相关操作。
  • ConcreteSubject(具体的被观察者):具体的被观察者对象,通常包含一些重要的数据,当数据发生变化时,会通知观察者。
  • ConcreteObserver(具体的观察者):具体的观察者对象,负责实现观察者接口,并在被观察者状态变化时作出响应。

工作流程

  1. 订阅/注册:观察者向被观察者注册自己,表示对其状态变化感兴趣。
  2. 状态改变:当被观察者的状态发生变化时,它会调用 notify() 方法,通知所有已注册的观察者。
  3. 通知/更新:观察者在接收到通知后,调用 update() 方法,从而获取被观察者的最新状态并更新自己的状态。

观察者模式的优点

  • 解耦:观察者和被观察者之间的依赖性很低,它们之间通过接口进行通信,可以轻松地添加、移除观察者,系统的可扩展性强。
  • 灵活性:可以在运行时动态地增加或移除观察者,观察者模式使得观察者的数量、种类都可以变化。
  • 一致性:被观察者可以保证所有观察者的状态和它自身的状态是一致的。

观察者模式的缺点

  • 性能开销:如果观察者数量众多,或者被观察者的状态更新频繁,通知所有观察者可能会带来较大的性能开销。
  • 通知顺序问题:在多个观察者的情况下,通知的顺序可能会影响程序的行为,而观察者模式通常不会对通知顺序做出严格规定。
  • 循环依赖风险:如果被观察者和观察者之间存在复杂的依赖关系,可能会导致循环更新或通知,从而引发问题。

适用场景

  • 状态变化的事件通知:当一个对象的状态变化需要通知其他对象时,例如用户界面的数据绑定。
  • 多级联动更新:多个对象之间需要保持一致性,例如在MVC架构中,模型数据变化需要通知视图更新。
  • 发布-订阅系统:例如消息推送系统,发布者发布消息后,所有订阅者都会收到通知。

现实生活中的例子

  • 新闻订阅:用户订阅某个新闻类别,当有新文章发布时,用户会收到通知。
  • 社交媒体:用户关注某个社交媒体账号,当该账号发布新内容时,用户会收到推送通知。
  • 气象站:气象站采集到新数据后,会通知所有的显示设备更新天气信息。

3.案例代码分析:

案例描述

假设我们有一个天气预报系统,系统有一个气象站(WeatherStation),它可以实时检测温度和湿度。用户可以通过不同的显示设备(如手机App、桌面小部件、电子看板等)查看天气信息。

当气象站检测到天气数据变化时,它会通知所有已注册的显示设备更新显示信息。这就是典型的观察者模式场景:气象站是“被观察者”,而显示设备是“观察者”。

代码实现

下面是使用C++实现的天气预报系统的观察者模式代码,包括详细注释。

#include <iostream>
#include <vector>
#include <algorithm>// 观察者接口:定义更新接口,用于接收通知
class Observer {
public:virtual void update(float temperature, float humidity) = 0;  // 更新函数,接收温度和湿度
};// 被观察者接口:定义注册、移除、通知观察者的方法
class Subject {
public:virtual void registerObserver(Observer* o) = 0;  // 注册观察者virtual void removeObserver(Observer* o) = 0;    // 移除观察者virtual void notifyObservers() = 0;              // 通知所有观察者
};// 具体的被观察者:气象站
class WeatherStation : public Subject {
private:std::vector<Observer*> observers;  // 观察者列表float temperature;                 // 当前温度float humidity;                    // 当前湿度public:// 注册观察者void registerObserver(Observer* o) override {observers.push_back(o);}// 移除观察者void removeObserver(Observer* o) override {observers.erase(std::remove(observers.begin(), observers.end(), o), observers.end());}// 通知所有观察者void notifyObservers() override {for (Observer* observer : observers) {observer->update(temperature, humidity);  // 通知每个观察者更新}}// 模拟天气数据的更新void setMeasurements(float temp, float hum) {temperature = temp;humidity = hum;notifyObservers();  // 数据更新后通知观察者}
};// 具体的观察者1:手机App显示器
class PhoneDisplay : public Observer {
public:void update(float temperature, float humidity) override {std::cout << "Phone Display - Temperature: " << temperature << "°C, Humidity: " << humidity << "%\n";}
};// 具体的观察者2:桌面小部件
class DesktopWidget : public Observer {
public:void update(float temperature, float humidity) override {std::cout << "Desktop Widget - Temperature: " << temperature << "°C, Humidity: " << humidity << "%\n";}
};// 具体的观察者3:电子看板
class ElectronicBillboard : public Observer {
public:void update(float temperature, float humidity) override {std::cout << "Electronic Billboard - Temperature: " << temperature << "°C, Humidity: " << humidity << "%\n";}
};int main() {WeatherStation weatherStation;  // 创建气象站对象PhoneDisplay phoneDisplay;      // 创建观察者对象:手机App显示器DesktopWidget desktopWidget;    // 创建观察者对象:桌面小部件ElectronicBillboard billboard;  // 创建观察者对象:电子看板weatherStation.registerObserver(&phoneDisplay);   // 注册观察者到气象站weatherStation.registerObserver(&desktopWidget);weatherStation.registerObserver(&billboard);// 模拟天气数据的更新weatherStation.setMeasurements(25.0f, 65.0f);weatherStation.setMeasurements(30.5f, 70.0f);// 移除一个观察者,模拟观察者取消订阅的情况weatherStation.removeObserver(&desktopWidget);// 再次更新天气数据,观察剩余观察者的反应weatherStation.setMeasurements(28.0f, 55.0f);return 0;
}

 

 

这篇关于【C++ | 设计模式】观察者模式的详解与实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1128882

相关文章

MySQL数据库双机热备的配置方法详解

《MySQL数据库双机热备的配置方法详解》在企业级应用中,数据库的高可用性和数据的安全性是至关重要的,MySQL作为最流行的开源关系型数据库管理系统之一,提供了多种方式来实现高可用性,其中双机热备(M... 目录1. 环境准备1.1 安装mysql1.2 配置MySQL1.2.1 主服务器配置1.2.2 从

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

MyBatis常用XML语法详解

《MyBatis常用XML语法详解》文章介绍了MyBatis常用XML语法,包括结果映射、查询语句、插入语句、更新语句、删除语句、动态SQL标签以及ehcache.xml文件的使用,感兴趣的朋友跟随小... 目录1、定义结果映射2、查询语句3、插入语句4、更新语句5、删除语句6、动态 SQL 标签7、ehc

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

从基础到高级详解Go语言中错误处理的实践指南

《从基础到高级详解Go语言中错误处理的实践指南》Go语言采用了一种独特而明确的错误处理哲学,与其他主流编程语言形成鲜明对比,本文将为大家详细介绍Go语言中错误处理详细方法,希望对大家有所帮助... 目录1 Go 错误处理哲学与核心机制1.1 错误接口设计1.2 错误与异常的区别2 错误创建与检查2.1 基础