突破编程_C++_设计模式(观察者模式)

1 观察者模式的概念

观察者模式(Observer Pattern)是设计模式中的一种行为模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,它的所有依赖者(观察者)都会收到通知并自动更新。

在 C++ 中,观察者模式通常包含以下几个关键部分:

(1)主题(Subject): 主题是一个包含观察者列表的抽象类或接口。它提供注册、移除和通知观察者的方法。当主题状态发生变化时,它会调用通知方法来更新所有注册的观察者。

(2)具体主题(Concrete Subject): 具体主题实现主题的接口,通常包含具体的状态或数据。当这些状态或数据发生变化时,它会通知观察者。

(3)观察者(Observer): 观察者是一个接口或抽象类,定义了观察者接收到通知时需要执行的操作。

(4)具体观察者(Concrete Observer): 具体观察者实现观察者的接口,并在接收到通知时执行具体的操作。

2 观察者模式的实现步骤

在 C++ 实现观察者模式的实现步骤如下:

(1)定义观察者接口(Observer):

  • 创建一个观察者接口,通常包含一个或多个纯虚函数,用于在主题状态发生变化时被调用。

(2)实现具体观察者(ConcreteObserver):

  • 创建一个或多个具体观察者类,实现观察者接口,并定义当接收到通知时需要执行的具体操作。

(3)定义主题接口(Subject):

  • 创建一个主题接口,包含注册观察者、移除观察者和通知观察者的方法。

(4)实现具体主题(ConcreteSubject):

  • 创建一个具体主题类,实现主题接口,并维护一个观察者列表。
  • 提供注册观察者方法,允许将观察者添加到观察者列表中。
  • 提供移除观察者方法,允许从观察者列表中移除特定的观察者。
  • 实现通知观察者方法,当主题状态发生变化时,遍历观察者列表并调用观察者的更新方法。

(5)在主题中维护观察者列表:

  • 具体主题类内部应该有一个容器(如std::vector、std::list等)来存储所有注册的观察者对象。

(6)实现通知机制:

  • 在具体主题类中,当状态发生变化或某些事件发生时,调用通知观察者方法。
  • 通知方法应该遍历观察者列表,并调用每个观察者的更新方法,传递必要的信息。

(7)注册与移除观察者:

  • 允许客户端代码在运行时注册新的观察者或移除已注册的观察者。
  • 注册操作应将观察者添加到观察者列表中,移除操作应从列表中删除指定的观察者。

(8)客户端使用:

  • 在客户端代码中,创建具体主题和具体观察者的实例。
  • 将具体观察者注册到具体主题中。
  • 当具体主题的状态发生变化时,它将自动通知所有注册的观察者。

通过以上步骤,C++ 中的观察者模式就实现了。它提供了一种松耦合的方式来处理对象之间的依赖关系,使得主题对象可以在不直接依赖具体观察者的情况下,通知它们状态的变化。这增强了代码的可维护性和可扩展性。

如下为样例代码:

#include <iostream>  
#include <vector>  
#include <memory>  
#include <algorithm> 
#include <string> // 观察者接口  
class Observer {
public:virtual ~Observer() = default;virtual void update(const std::string& message) = 0;
};// 具体观察者  
class ConcreteObserver : public Observer {
public:void update(const std::string& message) override {std::cout << "ConcreteObserver received: " << message << std::endl;}
};// 主题接口  
class Subject {
public:virtual ~Subject() = default;virtual void registerObserver(const std::shared_ptr<Observer>& observer) = 0;virtual void removeObserver(const std::shared_ptr<Observer>& observer) = 0;virtual void notifyObservers(const std::string& message) = 0;
};// 具体主题  
class ConcreteSubject : public Subject {
public:void registerObserver(const std::shared_ptr<Observer>& observer) override {observers.push_back(observer);}void removeObserver(const std::shared_ptr<Observer>& observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer),observers.end());}void notifyObservers(const std::string& message) override {for (auto& observer : observers) {observer->update(message);}}private:std::vector<std::shared_ptr<Observer>> observers;
};int main() 
{// 使用智能指针管理主题和观察者  std::shared_ptr<ConcreteSubject> subject = std::make_shared<ConcreteSubject>();std::shared_ptr<Observer> observer1 = std::make_shared<ConcreteObserver>();std::shared_ptr<Observer> observer2 = std::make_shared<ConcreteObserver>();// 注册观察者  subject->registerObserver(observer1);subject->registerObserver(observer2);// 通知观察者  subject->notifyObservers("Hello, Observers!");// 移除一个观察者  subject->removeObserver(observer1);// 再次通知观察者  subject->notifyObservers("Hello again, Observers!");return 0;
}

上面代码的输出为:

ConcreteObserver received: Hello, Observers!
ConcreteObserver received: Hello, Observers!
ConcreteObserver received: Hello again, Observers!

3 观察者模式的应用场景

C++ 中的观察者模式允许对象之间建立一种一对多的依赖关系,使得当一个对象状态发生改变时,它的所有依赖者(即观察者)都会收到通知并自动更新。这种设计模式在多种应用场景中都能发挥重要作用,以下是一些具体的应用场景:

(1)图形用户界面(GUI)开发:
当用户与GUI进行交互时,如点击按钮或拖动滑块,观察者模式可以用于处理这些事件。例如,按钮的点击事件可以被注册为观察者的多个组件监听,当按钮被点击时,所有监听该事件的组件都会收到通知并执行相应的操作。

(2)实时数据监控:
在需要实时监控数据变化的系统中,如股票交易系统、环境监测系统等,观察者模式可以用于在数据发生变化时通知所有相关的观察者。观察者可以根据接收到的数据更新其状态或执行其他操作。

(3)游戏开发:
在游戏中,角色的状态(如生命值、位置等)可能会频繁变化。通过使用观察者模式,游戏引擎可以在角色状态发生变化时通知所有相关的游戏对象(如界面元素、AI系统等),以便它们能够相应地更新或做出反应。

(4)消息传递和通知系统:
在分布式系统或微服务架构中,观察者模式可以用于实现发布/订阅机制。当某个服务发布消息或事件时,所有订阅了该消息或事件的观察者都会收到通知并进行处理。

(5)网络编程:
在网络编程中,服务器可能会接收到来自客户端的各种请求或消息。通过使用观察者模式,服务器可以将这些请求或消息广播给所有相关的处理器或观察者,以便它们能够进行相应的处理。

(6)多线程编程:
在多线程环境中,线程之间的通信和同步是一个重要问题。观察者模式可以用于实现线程之间的解耦和异步通知。当一个线程的状态或数据发生变化时,它可以通知其他线程进行相应的操作,而无需直接依赖或同步这些线程。

(7)配置文件或数据库变化监听:
当配置文件或数据库中的数据发生变化时,观察者模式可以用于通知相关的应用程序组件进行更新或重新加载配置。

3.1 观察者模式应用于图形用户界面(GUI)开发

下面是一个简化的示例,展示了如何在图形用户界面(GUI)开发中实现观察者模式。

首先,定义观察者接口和具体观察者:

#include <iostream>  
#include <memory>  
#include <vector>  
#include <algorithm> 
#include <string> // 观察者接口  
class Observer {  
public:  virtual ~Observer() = default;  virtual void update() = 0;  
};  // 具体观察者 - 处理按钮点击事件的类  
class ButtonClickHandler : public Observer {  
public:  void update() override {  std::cout << "Button was clicked! Handling the event..." << std::endl;  // 执行实际的按钮点击处理逻辑  }  
};

接下来,定义主题接口和具体主题:

// 主题接口  
class Subject {
public:virtual ~Subject() = default;virtual void registerObserver(const std::shared_ptr<Observer>& observer) = 0;virtual void removeObserver(const std::shared_ptr<Observer>& observer) = 0;virtual void notifyObservers() = 0;
};// 具体主题 - 代表一个按钮  
class Button : public Subject {
public:void registerObserver(const std::shared_ptr<Observer>& observer) override {observers.push_back(observer);}void removeObserver(const std::shared_ptr<Observer>& observer) override {observers.erase(std::remove(observers.begin(), observers.end(), observer),observers.end());}void notifyObservers() override {for (const auto& observer : observers) {observer->update();}}// 假设这是按钮被点击时调用的方法  void onClick() {std::cout << "Button clicked. Notifying observers..." << std::endl;notifyObservers();}private:std::vector<std::shared_ptr<Observer>> observers;
};

最后,在主函数中使用这些类来模拟 GUI 中的按钮点击事件处理:

int main() 
{// 创建具体观察者(按钮点击处理器)  std::shared_ptr<Observer> buttonClickHandler = std::make_shared<ButtonClickHandler>();// 创建具体主题(按钮)  std::shared_ptr<Subject> button = std::make_shared<Button>();// 将按钮转换为Button类型以便注册观察者  std::shared_ptr<Button> buttonPtr = std::static_pointer_cast<Button>(button);// 注册观察者到主题(将按钮点击处理器注册到按钮)  buttonPtr->registerObserver(buttonClickHandler);// 模拟按钮被点击的事件  buttonPtr->onClick();// 移除观察者(如果需要的话)  // buttonPtr->removeObserver(buttonClickHandler);  return 0;
}

上面代码的输出为:

Button clicked. Notifying observers...
Button was clicked! Handling the event...

上面代码使用了 std::shared_ptr 来管理观察者和主题的生命周期。当 std::shared_ptr 的引用计数变为0时,对应的对象会被自动删除。但是,请注意,这种方法可能会导致循环引用问题,如果主题和观察者相互持有对方的 std::shared_ptr,则它们将永远不会被删除。为了避免这种情况,通常建议仅在必要时使用 std::shared_ptr,并在可能的情况下使用原始指针、裸指针或 std::weak_ptr。

3.2 观察者模式应用于实时数据监控

以下是一个使用观察者模式实现实时数据监控的示例:

首先,定义观察者接口和主题接口:

#include <iostream>  
#include <memory>  
#include <vector>  
#include <mutex>  // 观察者接口  
class Observer {  
public:  virtual ~Observer() = default;  virtual void update(const double& data) = 0;  
};  // 主题接口  
class Subject {  
public:  virtual ~Subject() = default;  virtual void attach(const std::shared_ptr<Observer>& observer) = 0;  virtual void detach(const std::shared_ptr<Observer>& observer) = 0;  virtual void notify(const double& data) = 0;  
};

接着,实现具体的观察者和主题类:

// 具体观察者类  
class RealtimeDataObserver : public Observer {
public:void update(const double& data) override {std::cout << "Observer received new data: " << data << std::endl;// 处理实时数据的逻辑  }
};// 具体主题类  
class RealtimeDataSubject : public Subject {
public:void attach(const std::shared_ptr<Observer>& observer) override {std::lock_guard<std::mutex> lock(mtx);observers.push_back(observer);}void detach(const std::shared_ptr<Observer>& observer) override {std::lock_guard<std::mutex> lock(mtx);observers.erase(std::remove(observers.begin(), observers.end(), observer),observers.end());}void notify(const double& data) override {std::lock_guard<std::mutex> lock(mtx);for (const auto& observer : observers) {observer->update(data);}}private:std::vector<std::shared_ptr<Observer>> observers;mutable std::mutex mtx;
};

在上面的代码中,RealtimeDataSubject 维护了一个观察者列表,并在数据更新时调用所有观察者的 update 方法。使用 std::mutex 确保在多线程环境中对观察者列表的修改是线程安全的。

最后,在主函数中使用这些类来模拟实时数据监控:

int main() 
{// 创建主题对象  std::shared_ptr<Subject> subject = std::make_shared<RealtimeDataSubject>();// 创建观察者对象  std::shared_ptr<Observer> observer1 = std::make_shared<RealtimeDataObserver>();std::shared_ptr<Observer> observer2 = std::make_shared<RealtimeDataObserver>();// 将观察者附加到主题上  subject->attach(observer1);subject->attach(observer2);// 模拟实时数据更新  for (int i = 0; i < 5; ++i) {double newData = static_cast<double>(i); // 假设的实时数据  subject->notify(newData);// 可以在这里添加一些延迟以模拟实时数据更新的间隔  }// 移除某个观察者(如果需要)  // subject->detach(observer1);  return 0;
}

上面代码的输出为:

Observer received new data: 0
Observer received new data: 0
Observer received new data: 1
Observer received new data: 1
Observer received new data: 2
Observer received new data: 2
Observer received new data: 3
Observer received new data: 3
Observer received new data: 4
Observer received new data: 4

在这个例子中,创建了一个 RealtimeDataSubjec t对象和两个 RealtimeDataObserver 对象。当实时数据更新时,通过调用 notify 方法来通知所有观察者。此外,由于实时数据监控可能涉及多线程,所以使用了 std::mutex 来确保线程安全。在实际应用中,还应该考虑更复杂的并发控制策略,比如使用读写锁(std::shared_mutex)来优化性能,如果读操作远多于写操作的话。

4 观察者模式的优点与缺点

C++ 观察者模式的优点主要包括:

(1)解耦: 观察者模式降低了主题和观察者之间的耦合度。主题和观察者可以独立地改变和复用,只要他们遵守观察者模式的接口,就能无缝地集成在一起。

(2)灵活性: 可以动态地增加和删除观察者。这使得程序能够在运行时根据需求调整通知机制。

(3)支持广播通信: 主题可以通知多个观察者,无需知道它们的具体数量或类型。这使得实现一对多的通信变得非常简单。

(4)遵循开闭原则: 对扩展开放,对修改封闭。可以添加新的观察者类,而无需修改已有的主题类或观察者类。

然而,C++ 观察者模式也存在一些缺点:

(1)性能问题: 如果观察者数量非常多,或者通知操作非常耗时,那么每次主题状态变化时,都可能导致大量的通知操作,从而影响性能。

(2)依赖管理: 如果观察者之间也存在依赖关系,那么可能会导致复杂的依赖网络,使得代码难以理解和维护。

(3)内存泄漏风险: 如果使用 std::shared_ptr 而没有正确管理生命周期,可能会导致循环引用和内存泄漏。尽管可以通过 std::weak_ptr 来避免循环引用,但这会增加实现的复杂性。

(4)可能导致过度通知: 在某些情况下,可能不需要每次主题状态变化都通知所有观察者。过度的通知可能会浪费计算资源,并可能导致不必要的副作用。

(5)接口标准化: 观察者模式要求主题和观察者之间通过统一的接口进行通信。这可能导致接口过于复杂或不够灵活,以适应所有可能的观察者和主题类型。

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

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

相关文章

Git分支管理(IDEA)

文章目录 Git分支管理&#xff08;IDEA&#xff09;1.Git分支管理&#xff08;IDEA&#xff09;1.基本介绍1.分支理解2.示意图 2.搭建分支和合并的环境1.创建Gitee仓库2.创建普通maven项目3.克隆Gitee项目到E:\GiteeRepository4.复制erp文件夹下的内容到IDEA项目下5.IDEA项目中…

基于微信小程序的校园跑腿小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Django 模版基本语法

Django学习笔记 模版语法 本质&#xff1a;在HTML中写一些占位符&#xff0c;由数据对这些占位符进行替换和处理。 views.py def page2(request):#定义一些变量将变量传送给templates中的html文件name1 sallyname2 yingyinghobbys [swimming,badminton,reading]person {…

03-安装配置jenkins

一、安装部署jenkins 1&#xff0c;上传软件包 为了方便学习&#xff0c;本次给大家准备了百度云盘的安装包 链接&#xff1a;https://pan.baidu.com/s/1_MKFVBdbdFaCsOTpU27f7g?pwdq3lx 提取码&#xff1a;q3lx [rootjenkins ~]# rz -E [rootjenkins ~]# yum -y localinst…

音频分类革命:如何用Hugging Face实现前沿的音频频谱图变换器

目录 引言 ASTConfig 参数解释 示例代码及注释 ASTFeatureExtractor 参数解释 call 方法参数 ASTModel 参数 forward 方法参数 返回值 返回的主要元素 示例代码及说明 ASTForAudioClassification 参数 forward 方法参数 返回值 主要返回元素

【C语言】linux内核ip_generic_getfrag函数

一、讲解 这个函数ip_generic_getfrag是传输层用于处理分段和校验和的一个辅助函数&#xff0c;它通常用在IP层当需要从用户空间拷贝数据构建成网络数据包时。这个函数的实现提供了拷贝数据和进行校验和计算&#xff08;如果需要的话&#xff09;的功能。函数的参数解释如下&a…

[Spark SQL]Spark SQL读取Kudu,写入Hive

SparkUnit Function&#xff1a;用于获取Spark Session package com.example.unitlimport org.apache.spark.sql.SparkSessionobject SparkUnit {def getLocal(appName: String): SparkSession {SparkSession.builder().appName(appName).master("local[*]").getO…

如何优雅的比较两个对象是否相等

注意事项 使用 equals 方法&#xff1a;对于基本数据类型和包装类&#xff0c;可以直接使用 运算符进行比较。对于对象&#xff0c;应该使用 equals 方法进行比较&#xff0c;因为equals 方法考虑对象的实际内容&#xff0c;而 运算符比较的是对象的引用。 处理 null 值&…

【C#图解教程】笔记

文章目录 1. C#和.NET框架.NET框架的组成.NET框架的特点CLRCLICLI的重要组成部分各种缩写 2. C#编程概括标识符命名规则&#xff1a; 多重标记和值格式化数字字符串对齐说明符格式字段标准数字格式说明符标准数字格式说明符 表 3. 类型、存储和变量数据成员和函数成员预定义类型…

【机器学习】Adam优化算法

原理 Adam&#xff08;Adaptive Moment Estimation&#xff09;是一种常用的优化算法&#xff0c;结合了AdaGrad和RMSProp算法的优点。它通过自适应地调整学习率来优化神经网络模型的参数。 Adam算法的工作原理如下&#xff1a; 1. 初始化参数&#xff1a; 初始化模型的参数…

SpringCloud-基于SpringAMQP实现消息队列

一、Spring AMQP介绍 Spring AMQP作为Spring框架的一部分&#xff0c;是一套用于支持高级消息队列协议&#xff08;AMQP&#xff09;的工具。AMQP是一种强大的消息协议&#xff0c;旨在支持可靠的消息传递&#xff0c;特别适用于构建分布式系统。Spring AMQP构建在RabbitMQ之上…

三栏布局的实现方法

1. 什么是三栏布局 常见的一种页面布局方式&#xff0c;将页面分为左栏、中栏和右栏左右两侧的盒子宽度固定&#xff0c;中间的盒子会随屏幕自适应一般中间放主体内容&#xff0c;左右两边放辅助内容 2. 如何实现三栏布局 2.1 弹性布局 将最外层盒子设为弹性布局&#xff0…

爬虫入门到精通_框架篇16(Scrapy框架基本使用_名人名言的抓取

1 目标站点分析 抓取网站&#xff1a;http://quotes.toscrape.com/ 主要显示了一些名人名言&#xff0c;以及作者、标签等等信息&#xff1a; 点击next&#xff0c;page变为2&#xff1a; 2 流程框架 抓取第一页&#xff1a;请求第一页的URL并得到源代码&#xff0c;进行下…

Spring Cloud Gateway核心之Filter、自定义全局Filter、自定义局部Filter介绍

SpringCloudGateway-核心之Filter-自定义全局Filter-自定义局部Filter介绍 核心之Filter 路由过滤器允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。 路由过滤器的范围仅限于特定路由。 Spring Cloud Gateway 包含许多内置的 GatewayFilter Factory。 AddRequestHead…

基于鹦鹉优化算法(Parrot optimizer,PO)的无人机三维路径规划(提供MATLAB代码)

一、无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径&#xff0c;使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一&#xff0c;它可以通过算法和模型来确定无人机的航迹&#xff0c;以避开障碍物、优化飞行…

《计算机网络》考研:2024/3/7 2.1.4 奈氏准则和香农定理

2024/3/7 (作者转行去干LLMs了&#xff0c;但是又想搞定考研&#xff0c;忙不过来了就全截图了呜呜呜。。。 生活真不容易。) 2.1.4 奈氏准则与香农定理

RocketMQ、Kafka、RabbitMQ 消费原理,顺序消费问题【图文理解】

B站视频地址 文章目录 一、开始二、结果1、RocketMQ 消费关系图1-1、queue和consumer的关系1-2、consumer 和线程的关系 2、Kafka 消费关系图1-1、partitions和consumer的关系1-2、consumer 和线程的关系 3、RabbitMQ 消费关系图1-1、queue和consumer的关系1-2、consumer 和线程…

基于美洲狮优化算法(Puma Optimizar Algorithm ,POA)的无人机三维路径规划(提供MATLAB代码)

一、无人机路径规划模型介绍 无人机三维路径规划是指在三维空间中为无人机规划一条合理的飞行路径&#xff0c;使其能够安全、高效地完成任务。路径规划是无人机自主飞行的关键技术之一&#xff0c;它可以通过算法和模型来确定无人机的航迹&#xff0c;以避开障碍物、优化飞行…

【牛客】VL68 同步FIFO

描述 请设计带有空满信号的同步FIFO&#xff0c;FIFO的深度和宽度可配置。双口RAM的参考代码和接口信号已给出&#xff0c;请在答案中添加并例化此部分代码。 电路的接口如下图所示。端口说明如下表。 接口电路图如下&#xff1a; 双口RAM端口说明&#xff1a; 端口名I/O描述…

k8s中replication controller组件

背景 为了保证正常运行的pod的数量满足我们的要求&#xff0c;k8s中退出了replication controller的概念&#xff0c;这个组件的主要作用就是保证有指定数量的pod运行在集群中 replication controller组件 1.我们先看一下replication controller组件的配置文件定义 kind&am…