设计模式 —— 观察者模式
设计模式 —— 观察者模式
- 什么是观察者模式
- 观察者模式定义
- 观察者模式的角色
- 观察者模式的使用场景
- 观察者模式的实现
- 被观察者(Subject)
- 观察者(Observer)
- 通知(notify)
- 更新显示(update)
- 观察者模式的优缺点
- 优点
- 缺点
我们今天来介绍观察者模式:
什么是观察者模式
观察者模式定义
观察者模式(Observer Pattern)是一种行为型设计模式,它定义了对象间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会自动收到通知并更新。在这种模式中,一个目标对象(被观察对象)管理所有相依于它的观察者对象,并在其状态改变时主动发出通知。观察者模式通常被用来实现事件处理系统.
观察者模式的角色
观察者模式涉及以下几个核心角色:
- 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法.
- 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作.
- 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者.
- 具体观察者(Concrete Observer):具体观察者是观察者的具体实施类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作.
观察者模式的使用场景
观察者模式适用于以下场景:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这两者封装在独立的对象中以使它们可以各自独立地改变和复用.
- 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象需要被改变.
- 当一个对象必须通知其他对象,而它又不能假定其他对象是谁.
缺点:
-
在应用观察者模式时需要考虑一些开发小路问题,程序中包括一个被观察者和多个被观察者,开发和调试比较复杂.
-
在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)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。以下是观察者模式的主要优点和缺点:
优点
- 松耦合性(Decoupling):观察者模式通过抽象接口或抽象类定义了观察者和被观察者之间的交互,使得两者之间的依赖关系变得松散。这提高了系统的可维护性和可扩展性,因为修改一个类不会直接影响到其他类。
- 灵活性和动态性:可以很容易地在运行时动态添加新的观察者对象或移除现有观察者,而无需修改被观察者的代码,这使得系统非常灵活和易于扩展。
- 广播通知:被观察者可以一次性通知所有注册的观察者,减少了代码重复,并且能够确保状态的同步更新。
- 模块化:观察者模式促进了软件模块化设计,观察者和被观察者可以独立开发和测试,它们之间的交互通过接口标准化。
缺点
- 性能开销:当观察者数量很大时,通知所有观察者可能会引起性能问题,尤其是在每次状态变化都需要通知时。这可能涉及大量的遍历和调用操作。
- 过度通知:如果被观察者频繁改变状态,可能会导致不必要的通知,观察者可能接收到很多不必要的更新,增加了处理负担。
- 循环依赖和复杂性:如果观察者和被观察者之间形成了复杂的相互依赖关系,可能会导致难以理解和维护的循环引用问题,甚至系统死锁。
- 调试困难:由于观察者模式的异步和松耦合特性,有时很难跟踪和调试问题,尤其是当多个观察者同时响应并可能互相影响时。
总的来说,观察者模式适合那些需要维护多个对象间状态同步,且这些对象之间的关系可以抽象为一对多依赖的场景。但在应用时需要权衡其带来的灵活性和可能的性能、维护问题。
相关文章:
设计模式 —— 观察者模式
设计模式 —— 观察者模式 什么是观察者模式观察者模式定义观察者模式的角色观察者模式的使用场景观察者模式的实现 被观察者(Subject)观察者(Observer)通知(notify)更新显示(update)…...
光纤跳线(又称光纤连接器)的种类
光纤跳线(又称光纤连接器),也就是接入光模块的光纤接头,也有好多种,且相互之间不可以互用。SFP模块接LC光纤连接器,而GBIC接的是SC光纤连接器。下面对网络工程中几种常用的光纤连接器进行详细的说明&#x…...
探索Ubuntu:从入门到精通
目录 一、什么是Ubuntu? 1.1 Ubuntu的定义和背景 1.2 Ubuntu的特点 二、安装Ubuntu 2.1 下载Ubuntu安装镜像 2.2 制作启动盘 2.3 安装Ubuntu 三、初步设置和基本操作 3.1 系统更新 3.2 安装必要软件 3.3 设置和管理用户账户 四、文件和目录管理 4.1 文件管理器 …...
SpringMVC-基础架构
一、什么是MVC 二、什么是SpringMVC 三、SpringMVC的特点 四、配置SpringMVC 简单流程: 总体框架 1.创建pom.xml依赖 <!--打包方式--><packaging>war</packaging><!--依赖--><dependencies><dependency><groupId>org.s…...
《Windows API每日一练》4.1 GDI绘图
本节必须掌握的知识点: GDI原理 GDI函数调用 GDI基本图形 4.1.1 GDI原理 GDI,全称是Graphics Device Interface(图形设备接口),是微软Windows操作系统中提供的一套用于渲染图形和格式化文本的API(应用程序…...
SQL Server 安装后,服务器再改名,造成名称不一致,查询并修改数据库服务器真实名称
SELECT SERVERNAME -- 1.查询旧服务器名称 SELECT serverproperty(servername) AS new --2.查询新服务器名称 -- 3.更新服务器名称 IF SERVERPROPERTY(servername) <> 新服务器名称替换 BEGIN DECLARE server_name NVARCHAR(128) SET server_name 新服务器…...
单例模式、工厂模式 c++关键字 static
static 关键字的作用: 主要作用在于 控制变量或函数的作用域、生命周期以及它们如何被不同部分的程序访问,从而帮助程序员管理内存、避免命名冲突,并实现特定的设计模式(如单例模式)。 1. 静态局部变量:当…...
基于文本和图片输入的3D数字人化身生成技术解析
随着虚拟现实、增强现实和元宇宙等技术的飞速发展,对高度逼真且具有表现力的3D数字人化身的需求日益增长。传统的3D数字人生成方法往往需要依赖大量的3D数据集,这不仅增加了数据收集和处理的成本,还限制了生成的多样性和灵活性。为了克服这些挑战,我们提出了一种基于文本提…...
C语言 | Leetcode C语言题解之第150题逆波兰表达式求值
题目: 题解: int evalRPN(char** tokens, int tokensSize) {int n tokensSize;int stk[(n 1) / 2];memset(stk, 0, sizeof(stk));int index -1;for (int i 0; i < n; i) {char* token tokens[i];if (strlen(token) > 1 || isdigit(token[0])…...
API安全性的重要性及实施策略
在当今日益互联的世界中,API(应用程序编程接口)成为连接不同软件系统的关键桥梁。随着API的使用越来越广泛,其安全性问题也日益凸显。一个不安全的API可能会使企业数据和用户信息面临严重的风险。因此,确保API的安全性…...
现在Java行情不好可以转.net吗?
转向.NET开发可能是一个选择,但要注意以下几点。我这里有一套编程入门教程,不仅包含了详细的视频 讲解,项目实战。如果你渴望学习编程,不妨点个关注,给个评论222,私信22,我在后台发给你。 技术转…...
大文件word生成的处理与解决策略
前言 对于简单word文档的生成导出,java已经有着很多技术来进行处理,在有着相对固定的格式样板下,采用word模板导出相对会是比较好的选择。但是当数据量且包含大量图片后,采用模板导出就显得无力了,模板的缺点是无法应…...
unity3d:GameFramework+xLua+Protobuf+lua-protobuf,与服务器交互收发协议
概述 1.cs收发协议,通过protobuf序列化 2.lua收发协议,通过lua-protobuf序列化 一条协议字节流组成 C#协议基类 CSPacketBase,SCPacketBaseC#用协议基类 proto生成的CS类,基于这两个基类。分别为CSPacketBase是客户端发送至服…...
二刷算法训练营Day30 | 回溯算法(6/6)
目录 详细布置: 1. 回溯总结 2. 332. 重新安排行程 3. 51. N 皇后 4. 37. 解数独 详细布置: 1. 回溯总结 回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起&#x…...
【车载AI音视频电脑】200万像素迷你一体机
产品主要特点: -设备安装方便简洁,可通过3M胶直接将设备粘 贴到车前挡风玻璃上 -支持IE预览,手机,PAD实时预览, 支持电脑客 户端实时预览功能 -内置2路模拟高清, 每路均可达到200万像素。另 外可扩充2路1080P模拟…...
齐普夫定律在循环神经网络中的语言模型的应用
目录 齐普夫定律解释公式解释图与公式的关系代码与图的分析结论 使用对数表达方式的原因1. 线性化非线性关系2. 方便数据可视化和分析3. 降低数值范围4. 方便参数估计公式详细解释结论 来自:https://zh-v2.d2l.ai/chapter_recurrent-neural-networks/language-model…...
如何在Android Studio上发布Flutter应用
发布Flutter应用到Android平台是一个多步骤的过程,涉及配置应用、生成签名密钥、配置Gradle文件、构建发布版本APK等步骤。本文将详细介绍这些步骤,帮助你顺利发布Flutter应用。 1. 准备你的应用 在发布之前,确保你的应用在开发环境中运行良…...
C++ 字符串处理4-根据指定的分隔符将字符串分割为多个子串根据指定的分隔符将多个子串连接成一个字符串
1. 关键词 C 字符串处理 分割字符串 连接字符串 跨平台 2. strutil.h #pragma once#include <string> #include <vector>namespace cutl {/*** brief The type of vector strings used in this library.**/using strvec std::vector<std::string>;/*** b…...
微信小程序请求request封装
公共基础路径封装 // config.js module.exports {// 测试BASE_URL: https://cloud.chejj.cn,// 正式// BASE_URL: https://cloud.mycjj.com };请求封装 // request.js import config from ../config/baseUrl// 请求未返回时的loading const showLoading () > wx.showLoadi…...
Web前端不挂科:深入探索与实战指南
Web前端不挂科:深入探索与实战指南 在数字化时代的浪潮中,Web前端开发已成为一项炙手可热的技能。然而,对于许多初学者来说,如何避免在Web前端课程中挂科却成为了一道难题。本文将从四个方面、五个方面、六个方面和七个方面&…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
从“安全密码”到测试体系:Gitee Test 赋能关键领域软件质量保障
关键领域软件测试的"安全密码":Gitee Test如何破解行业痛点 在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的"神经中枢"。从国防军工到能源电力,从金融交易到交通管控,这些关乎国计民生的关键领域…...
Python网页自动化Selenium中文文档
1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API,让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API,你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...
客户案例 | 短视频点播企业海外视频加速与成本优化:MediaPackage+Cloudfront 技术重构实践
01技术背景与业务挑战 某短视频点播企业深耕国内用户市场,但其后台应用系统部署于东南亚印尼 IDC 机房。 随着业务规模扩大,传统架构已较难满足当前企业发展的需求,企业面临着三重挑战: ① 业务:国内用户访问海外服…...
