设计模式 —— 观察者模式

设计模式 —— 观察者模式

  • 什么是观察者模式
      • 观察者模式定义
      • 观察者模式的角色
      • 观察者模式的使用场景
      • 观察者模式的实现
  • 被观察者(Subject)
  • 观察者(Observer)
  • 通知(notify)
  • 更新显示(update)
  • 观察者模式的优缺点
      • 优点
      • 缺点

我们今天来介绍观察者模式

什么是观察者模式

观察者模式定义

观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会自动收到通知并更新。在这种模式中,一个目标对象(被观察对象)管理所有相依于它的观察者对象,并在其状态改变时主动发出通知。观察者模式通常被用来实现事件处理系统.

观察者模式的角色

观察者模式涉及以下几个核心角色:

  1. 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法.
  1. 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作.
  1. 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者.
  1. 具体观察者(Concrete Observer):具体观察者是观察者的具体实施类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作.

观察者模式的使用场景

观察者模式适用于以下场景:

  1. 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这两者封装在独立的对象中以使它们可以各自独立地改变和复用.
  1. 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象需要被改变.
  1. 当一个对象必须通知其他对象,而它又不能假定其他对象是谁.

缺点:

  • 在应用观察者模式时需要考虑一些开发小路问题,程序中包括一个被观察者和多个被观察者,开发和调试比较复杂.

  • 在Java中的消息的通知默认是顺序执行的,一个观察者的卡顿会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式.

观察者模式的实现

实现观察者模式有多种形式,一种直观的方式是使用“注册—通知—撤销注册”的形式。观察者将自己注册到被观察对象中,被观察对象将观察者存放在一个容器里。当被观察对象发生了某种变化,它从容器中得到所有注册过的观察者,将变化通知观察者。观察者告诉被观察对象要撤销观察,被观察对象从容器中将观察者去除.

我们举个例子:玩家攻击怪兽掉血显示血量,增加气势

被观察者(Subject)

怪兽(或其血量管理类)扮演“被观察者(Subject)”角色,负责维持血量状态并管理观察者列表。

// 定义被观察者接口,任何能够被观察的状态持有者(如怪物)需要实现这个接口
class Subject {
public:// 虚析构函数,确保通过基类指针删除子类对象时能正确调用子类析构函数virtual ~Subject() {}// attach: 注册观察者到被观察者,使其可以接收状态变更通知virtual void attach(Observer* observer) = 0;// detach: 解注册观察者,不再接收状态变更通知virtual void detach(Observer* observer) = 0;// notify: 通知所有观察者血量变化virtual void notify(int health) = 0;// notifyMoraleChange: 通知所有观察者气势变化virtual void notifyMoraleChange() = 0;
};// 怪物类,继承自被观察者接口,表示它是可被观察的状态持有者
class Monster : public Subject {
public:// 构造函数,初始化怪物的血量为10Monster() : _health(100), _morel(0) {}// 析构函数,清理资源,虽然当前版本未直接管理额外资源,但保持以备未来扩展~Monster() {}private:// _health: 怪物的当前血量int _health;// _morel: 怪物的当前气势值int _morale;// _observers: 存储所有观察怪物状态的观察者指针集合std::vector<Observer*> _observers;
};

观察者(Observer)

UI显示组件作为“观察者(Observer)”,订阅怪兽的血量变化。

// 定义观察者接口,任何想要监听怪物状态变化的实体都需要实现这个接口
class Monster : public Subject
public:// updateHealthy: 当怪物血量发生变化时,观察者会被通知virtual void updateHealthy(int health) = 0;// updateMorale: 当怪物气势发生变化时,观察者会被通知virtual void updateMorale(int morale) = 0;
};

通知(notify)

当玩家的攻击导致怪兽血量减少时,怪兽对象通知所有观察者(即UI组件)。

    // 通知所有观察者血量变化,调用观察者的 updateHealthy 方法。void notifyHeath(int health) override {for(auto & observer: _observers) {observer->updateHealthy(health); // 应修正参数传递,确保观察者获得实际的血量值。}}// 通知所有观察者气势变化,调用观察者的 updateMorel 方法。void notifyMoraleChange() override {for(auto & observer: _observers) {observer->updateMorel(_morale); // 同样,传递当前气势值给观察者。}}void takeDamage(int damage){_health -= damage;if (_health < 0) _health = 0;_morale += 20; // 气势增加notifyHeath(_health); // 通知血量变化notifyMoraleChange(); // 新增:通知气势变化}

更新显示(update)

观察者收到通知后,各自更新显示的血量信息。

//属性条
class Classbuff : public Observer
{
public:void updateHealthy(int health) override{std::cout << "Health Bar Update: Current HP is " << health << std::endl;}void updateMorel(int morale) override{std::cout << "Morale Update: Current Morale is " << morale << std::endl;}
};

完整代码如下:

// 使用#pragma once防止头文件重复包含
#pragma once// 引入所需的标准库
#include<iostream>
#include<vector>// **观察者类定义**
// 观察者接口,任何观察怪物状态的类需要实现这两个更新方法
class Observer {
public:// 纯虚函数,更新血量virtual void updateHealthy(int health) = 0;// 纯虚函数,更新气势virtual void updateMorel(int morale) = 0;
};// **被观察者接口定义**
// 定义被观察者需要实现的接口,用于管理观察者列表及通知状态变化
class Subject {
public:// 虚析构函数,确保通过基类指针可以安全删除子类对象virtual ~Subject() {}// 接口方法,注册观察者virtual void attach(Observer* observer) = 0;// 接口方法,注销观察者virtual void detach(Observer* observer) = 0;// 接口方法,通知所有观察者血量变化virtual void notifyHeath() = 0;// 接口方法,通知所有观察者气势变化virtual void notifyMoraleChange() = 0;
};// **Monster类定义**
// 继承自Subject,代表被观察者(怪物)
class Monster : public Subject {
public:// 默认构造函数,设置初始血量为100Monster() :_health(100) {}// 构造函数,允许设置初始血量Monster(int health) :_health(health) {}// 析构函数,删除所有观察者对象(假设Monster拥有观察者对象所有权)~Monster() {for(auto observer : _observers) {delete observer;}}// 实现attach方法,添加观察者到列表void attach(Observer* observer) override {_observers.push_back(observer);}// 实现detach方法,从列表中移除指定观察者void detach(Observer* observer) override {for(auto it = _observers.begin(); it != _observers.end(); ) {if(*it == observer) {it = _observers.erase(it);} else {++it;}}}// 实现notifyHeath,通知观察者血量变化void notifyHeath() override {for(auto observer: _observers) {observer->updateHealthy(_health);}}// 实现notifyMoraleChange,通知观察者气势变化void notifyMoraleChange() override {for(auto observer: _observers) {observer->updateMorel(_morale);}}// 减少怪物血量并增加气势,同时通知观察者void takeDamage(int damage) {_health -= damage;if (_health < 0) _health = 0;_morale += 20; // 气势增加notifyHeath(); // 通知血量变化notifyMoraleChange(); // 通知气势变化}private:int _health; // 怪物的血量int _morale; // 怪物的气势,默认为0std::vector<Observer*> _observers; // 存储观察者指针的向量
};// **Classbuff类定义**
// 实现Observer接口,代表一个具体的观察者(如血量条)
class Classbuff : public Observer {
public:// 实现更新血量显示void updateHealthy(int health) override {std::cout << "Health Bar Update: Current HP is " << health << std::endl;}// 实现更新气势显示void updateMorel(int morale) override {std::cout << "Morale Update: Current Morale is " << morale << std::endl;}
};

这段代码通过观察者模式展示了如何设计一个怪物类(Monster)和一个观察者类(Classbuff)。怪物类负责维护血量和气势状态,并在状态变化时通知所有注册的观察者。Classbuff类作为观察者,负责接收通知并打印出怪物的血量或气势变化信息。

我们来试试:

#define _CRT_SECURE_NO_WARNINGS 1
//#include "Monster.h"#include "monster_2.h"int main()
{Monster monster(100); // 创建一个初始血量为100的怪兽Classbuff* healthBar = new Classbuff(); // 使用指针以匹配detach操作// 怪兽注册生命条观察者monster.attach(healthBar);// 玩家攻击,造成20点伤害monster.takeDamage(20);// 假设需要在某个时刻移除观察者// monster.detach(healthBar);return 0;}

在这里插入图片描述

观察者模式的优缺点

观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。以下是观察者模式的主要优点和缺点:

优点

  1. 松耦合性(Decoupling):观察者模式通过抽象接口或抽象类定义了观察者和被观察者之间的交互,使得两者之间的依赖关系变得松散。这提高了系统的可维护性和可扩展性,因为修改一个类不会直接影响到其他类。
  1. 灵活性和动态性:可以很容易地在运行时动态添加新的观察者对象或移除现有观察者,而无需修改被观察者的代码,这使得系统非常灵活和易于扩展。
  1. 广播通知:被观察者可以一次性通知所有注册的观察者,减少了代码重复,并且能够确保状态的同步更新。
  1. 模块化:观察者模式促进了软件模块化设计,观察者和被观察者可以独立开发和测试,它们之间的交互通过接口标准化。

缺点

  1. 性能开销:当观察者数量很大时,通知所有观察者可能会引起性能问题,尤其是在每次状态变化都需要通知时。这可能涉及大量的遍历和调用操作。
  1. 过度通知:如果被观察者频繁改变状态,可能会导致不必要的通知,观察者可能接收到很多不必要的更新,增加了处理负担。
  1. 循环依赖和复杂性:如果观察者和被观察者之间形成了复杂的相互依赖关系,可能会导致难以理解和维护的循环引用问题,甚至系统死锁。
  1. 调试困难:由于观察者模式的异步和松耦合特性,有时很难跟踪和调试问题,尤其是当多个观察者同时响应并可能互相影响时。

总的来说,观察者模式适合那些需要维护多个对象间状态同步,且这些对象之间的关系可以抽象为一对多依赖的场景。但在应用时需要权衡其带来的灵活性和可能的性能、维护问题。

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

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

相关文章

Apache Pulsar 从入门到精通

一、快速入门 Pulsar 是一个分布式发布-订阅消息平台&#xff0c;具有非常灵活的消息模型和直观的客户端 API。 最初由 Yahoo 开发&#xff0c;在 2016 年开源&#xff0c;并于2018年9月毕业成为 Apache 基金会的顶级项目。Pulsar 已经在 Yahoo 的生产环境使用了三年多&#…

26-LINUX--I/O复用-select

一.I/O复用概述 /O复用使得多个程序能够同时监听多个文件描述符&#xff0c;对提高程序的性能有很大帮助。以下情况适用于I/O复用技术&#xff1a; ◼ TCP 服务器同时要处理监听套接字和连接套接字。 ◼ 服务器要同时处理 TCP 请求和 UDP 请求。 ◼ 程序要同时处理多个套接…

Python 连接 MySQL 及 SQL增删改查(主要使用sqlalchemy)

目录 一、环境 二、MySQL的连接和使用 2.1方式一&#xff1a;sql为主 2.1.1创建连接 2.1.2 表结构 2.1.3 新增数据 ​编辑 2.1.4 查看数据 ​编辑 2.1.5 修改数据 2.1.6 删除数据 2.2方式二&#xff1a;orm对象关系映射 2.2.1 mysql连接 2.2.2 创建表 2.2.3 新增…

关于 Redis 中集群

哨兵机制中总结到&#xff0c;它并不能解决存储容量不够的问题&#xff0c;但是集群能。 广义的集群&#xff1a;只要有多个机器&#xff0c;构成了分布式系统&#xff0c;都可以称之为一个“集群”&#xff0c;例如主从结构中的哨兵模式。 狭义的集群&#xff1a;redis 提供的…

Java里面的10个Lambda表达式必须掌握,提高生产力

目录 Java里面的10个Lambda表达式必须掌握&#xff0c;提高生产力 前言 1. 使用Lambda表达式进行集合遍历 2. 使用Lambda表达式进行集合过滤 3. 使用Lambda表达式进行集合映射 4. 使用Lambda表达式进行集合排序 5. 使用Lambda表达式进行集合归约 6. 使用Lambda表达式进…

使用docker-compose搭建达梦数据库主备集群

目录 1. Docker集群的搭建 2. 检查主备数据库 3. 主备集群的JDBC连接设置 1. Docker集群的搭建 达梦的镜像文件都是tar文件&#xff0c;通过docker load命令导入&#xff1a; docker load -i dm8_20240422_x86_rh6_64_rq_ent_8.1.3.140.tar 成功导入后&#xff0c;可看到…

刚刚❗️德勤2025校招暑期实习测评笔试SHL测评题库已发(答案)

&#x1f4e3;德勤 2024暑期实习测评已发&#xff0c;正在申请的小伙伴看过来哦&#x1f440; ㊙️本次暑期实习优先考虑2025年本科及以上学历的毕业生&#xff0c;此次只有“审计及鉴定”“税务与商务咨询”两个部门开放了岗位~ ⚠️测评注意事项&#xff1a; &#x1f44…

【JAVASE】java语法(成员变量与局部变量的区别、赋值运算符中的易错点)

一&#xff1a;成员变量与局部变量的区别 区别 成员变量 局部变量 类中位置不同 …

Java:110-SpringMVC的底层原理(上篇)

SpringMVC的底层原理 在前面我们学习了SpringMVC的使用&#xff08;67章博客开始&#xff09;&#xff0c;现在开始说明他的原理&#xff08;实际上更多的细节只存在67章博客中&#xff0c;这篇博客只是讲一点深度&#xff0c;重复的东西尽量少说明点&#xff09; MVC 体系结…

2024 AEE | 风丘科技将亮相日本爱知国际会展中心——共同创造!

2024年名古屋汽车工程博览会&#xff08;Automotive Engineering Exposition 2024 NAGOYA&#xff09;将于7月17-19日在日本爱知县国际展示场&#xff08;Aichi Sky Expo&#xff09;开展。本展会是专门为活跃在汽车行业的工程师和研究人员举办的汽车技术展览&#xff0c;汇聚了…

Web自动化测试-掌握selenium工具用法,使用WebDriver测试Chrome/FireFox网页(Java

目录 一、在Eclipse中构建Maven项目 1.全局配置Maven 2.配置JDK路径 3.创建Maven项目 4.引入selenium-java依赖 二、Chrome自动化脚本编写 1.创建一个ChromeTest类 2.测试ChromeDriver 3.下载chromedriver驱动 4.在脚本中通过System.setProperty方法指定chromedriver的…

操作系统期末复习整理知识点

操作系统的概念&#xff1a;①控制和管理整个计算机系统的硬件和软件资源&#xff0c;并合理地组织调度计算机的工作和资源的分配&#xff1b;②提供给用户和其他软件方便的接口和环境&#xff1b;③是计算机中最基本的系统软件 功能和目标&#xff1a; ①操作系统作为系统资源…

搭贝请假审批应用

在现代企业管理中&#xff0c;高效的请假审批系统至关重要。搭贝的请假审批应用通过简化员工的请假流程、提升管理层的工作效率&#xff0c;确保企业运作的连贯性和透明度。本文将介绍搭贝请假审批应用的主要功能模块&#xff1a;请假分析看板、请假申请审批流、请假类型维护和…

RocketMq源码解析六:消息存储

一、消息存储核心类 rocketmq消息存储的功能主要在store这个模块下。 核心类就是DefaultMessageStore。我们看下其属性 // 配置文件 private final MessageStoreConfig messageStoreConfig; // CommitLog 文件存储实现类 private final CommitLog commitLog; …

Mybatis05-一对多和多对一处理

多对一和一对多 多对一 多对一的理解&#xff1a; 多个学生对应一个老师 如果对于学生这边&#xff0c;就是一个多对一的现象&#xff0c;即从学生这边关联一个老师&#xff01; 结果映射&#xff08;resultMap&#xff09;&#xff1a; association 一个复杂类型的关联&…

PHP实现抖音小程序用户登录获取openid

目录 第一步、抖音小程序前端使用tt.login获取code 第二步、前端拿到code传给后端 第三步、方法1 后端获取用户信息 第四步、方法2 抖音小程序拿到用户信息把用户信息传给后端 code2Session抖音小程序用户登录后端文档 第一步、抖音小程序前端使用tt.login获取code 前端 …

leetcode 所有可能的路径(图的遍历:深度优先和广度优先)

leetcode 链接&#xff1a; 所有可能的路径 1 图的基本概念 1.1 有向图和无向图 左边是有向图&#xff0c;右边是无向图。对于无向图来说&#xff0c;图中的边没有方向&#xff0c;两个节点之间只可能存在一条边&#xff0c;比如 0 和 1 之间的边&#xff0c;因为是无向图&am…

线性代数|机器学习-P12Ax=b条件下x最小值问题

文章目录 1. Axb下的最值问题-图形转换2. Gram-Schmidt 标准形3. 迭代法-Krylov子空间法 1. Axb下的最值问题-图形转换 假设我们有一个直线方程如下&#xff1a; 3 x 1 4 x 2 1 \begin{equation} 3x_14x_21 \end{equation} 3x1​4x2​1​​ 在二维平面上&#xff0c;各个范…

DDMA信号处理以及数据处理的流程---原始数据生成

Hello&#xff0c;大家好&#xff0c;我是Xiaojie&#xff0c;好久不见&#xff0c;欢迎大家能够和Xiaojie一起学习毫米波雷达知识&#xff0c;Xiaojie准备连载一个系列的文章—DDMA信号处理以及数据处理的流程&#xff0c;本系列文章将从目标生成、信号仿真、测距、测速、cfar…

LLVM 后端执行流程

异构计算程序工作流程 图4-1中的LLVM后端的主要功能是代码生成&#xff0c;其中包括若干指令生成分析转换pass&#xff0c;将LLVM IR 转换为特定目标架构的机器代码 LLVM 流水线结构 输入指令经过图4-2中的各个阶段&#xff0c;从最初的LLVM IR&#xff0c;逐步演化为Selectio…