设计模式 —— 观察者模式
设计模式 —— 观察者模式
- 什么是观察者模式
- 观察者模式定义
- 观察者模式的角色
- 观察者模式的使用场景
- 观察者模式的实现
- 被观察者(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前端课程中挂科却成为了一道难题。本文将从四个方面、五个方面、六个方面和七个方面&…...
Golang | Leetcode Golang题解之第149题直线上最多的点数
题目: 题解: func maxPoints(points [][]int) (ans int) {n : len(points)if n < 2 {return n}for i, p : range points {if ans > n-i || ans > n/2 {break}cnt : map[int]int{}for _, q : range points[i1:] {x, y : p[0]-q[0], p[1]-q[1]if…...
京准电钟 NTP时间同步服务器助力水库水坝水利自动化建设
京准电钟 NTP时间同步服务器助力水库水坝水利自动化建设 京准电钟 NTP时间同步服务器助力水库水坝水利自动化建设 水库大坝监测系统主要包括渗流监测系统、流量监测系统、雨量监测系统、沉降监测系统组成。每一个监测系统由监测仪器及自动化数据采集装置(内置通信装…...
程序员应该具备什么职业素养?
程序员应该有什么职业素养? 作为一个程序员,拥有以下职业素养是非常重要的: 扎实的技术功底:作为程序员,首先要具备扎实的技术基础,包括编程语言、算法、数据结构等方面的知识,能够熟练地解决问…...
linux 安装sftp及使用sftp上传和下载
一、centos7 安装sftp 1.安装 OpenSSH 服务: sudo yum install openssh-server2.启动 SSH 服务,并设置为开机启动: sudo systemctl start sshd sudo systemctl enable sshd3.创建一个新用户,用于SFTP连接(替换your_…...
AI虚拟试穿技术:开启高保真、多场景、多样化服装组合的试穿应用
随着电子商务的快速发展,消费者对于在线购物体验的要求越来越高。特别是在服装领域,消费者渴望能够在购买前直观地了解服装的试穿效果。传统的虚拟试穿技术虽然已有一定的发展,但在不同场景下的高保真度和鲁棒性方面仍面临挑战。为此,我们研发了一种全新的AI虚拟试穿技术,…...
数栈xAI:轻量化、专业化、模块化,四大功能革新 SQL 开发体验
在这个数据如潮的时代,SQL 已远远超越了简单的查询语言范畴,它已成为数据分析和决策制定的基石,成为撬动企业智慧决策的关键杠杆。SQL 的编写和执行效率直接关系到数据处理的速度和分析结果的深度,对企业洞察市场动态、优化业务流…...
oppo手机精简包名列表
oppo广告机,coloros为13.0,测试机为oppo a1x 5g。 手机第一次开机后就全屏广告,被恶心了好几个月。现使用universal Android debolater进行卸载测试,其中: 不可卸载的: 开机广告:com.coloros.…...
Cisco Packet Tracer实验(二)
二、用交换机构建 LAN 构建物件如下: 四个PC 两个交换机 一个Multi Switch多功能拓展控制器 连线必须是这个直线!!!不是虚线 最后实现效果如下: 全部的线是绿的,就表示是通的。 尝试一下,看PC…...
Julia 数学函数
Julia 数学函数 Julia 是一种高性能的动态编程语言,特别适合于数值计算和科学计算。在数学领域,Julia 提供了丰富的内置函数,这些函数涵盖了从基本运算到高级数学运算的各个方面。本文将详细介绍 Julia 中的数学函数,并提供一些示例,帮助读者更好地理解和使用这些函数。 …...
[next.js] svgr/webpack
nextjs如何配置svg文件,使其像react组件一样导入? 当前next.js 开发环境我使用了--turbo 来开启turbopack加速文件构建,所以之前的一些webpack loader之类的无法正常工作。通过搜索发现一般都是使用svgr/webpack来处理svg,打开svgr官网发现…...
做暧暖爱视频每一刻网站/东莞网络推广策略
import os.path import os.path as op os.path.abspath(path) #返回path在当前系统中的绝对路径 os.path.normpath(path) #归一化path的表示形式,统一用\\分隔路径 os.path.relpath(path) #返回当前程序与文件之间的相对路径 os.path.dirname(path) #返回path中的目…...
深圳做网站报价/网站推广公司黄页
NOI OpenJudge 2971:抓住那头牛 无脑bfs?还不懂为什么我之前的方法不行……noijudge 看不到 部分数据…… #include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std;int N, K; bool vis[100010 …...
网站建设需要做的事情/哪里可以买链接网站
1、用户向服务器发送用户名和密码。 2、服务端收到请求,去验证用户名与密码 3、验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端。 4、客户端收到 Token 以后可以把它存储起来,比如放在 Cookie 里或者 Local Stor…...
网站建设7个基本流程/淘宝搜索指数
综述:文本分析在市场营销研究中的应用文本大数据分析在经济学和金融学中的应用:一个文献综述倒计时4天|Python&Stata数据分析课寒假工作坊大数据时代到来,网络数据正成为潜在宝藏,大量商业信息、社会信息以文本等非结构化、异…...
wordpress语言包编辑/优化设计官方电子版
序:为什么写poi,zy说poi都是思路题目,不像hnoi妈的数据结构队。。。。。 1.bzoj1102 题目大意:定义了一个山谷和山峰,求他们数量。 题解:这种题bfs咯,在bfs的时候记录一下相邻的比我大的有多少&…...
会泽做网站/知乎关键词排名
许可是VDI的关键组件。VMware对Horizon View提供了两个主要的许可选项:单个用户模型和单个并发连接模型。 VMware Horizon View,以前称为VMware View,是Horizon标准版的一部分。因此,希望使用Horizon View的组织必须对Horizon Sta…...