当前位置: 首页 > news >正文

设计模式 —— 观察者模式

设计模式 —— 观察者模式

  • 什么是观察者模式
      • 观察者模式定义
      • 观察者模式的角色
      • 观察者模式的使用场景
      • 观察者模式的实现
  • 被观察者(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. 调试困难:由于观察者模式的异步和松耦合特性,有时很难跟踪和调试问题,尤其是当多个观察者同时响应并可能互相影响时。

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

相关文章:

设计模式 —— 观察者模式

设计模式 —— 观察者模式 什么是观察者模式观察者模式定义观察者模式的角色观察者模式的使用场景观察者模式的实现 被观察者&#xff08;Subject&#xff09;观察者&#xff08;Observer&#xff09;通知&#xff08;notify&#xff09;更新显示&#xff08;update&#xff09…...

光纤跳线(又称光纤连接器)的种类

光纤跳线&#xff08;又称光纤连接器&#xff09;&#xff0c;也就是接入光模块的光纤接头&#xff0c;也有好多种&#xff0c;且相互之间不可以互用。SFP模块接LC光纤连接器&#xff0c;而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 简单流程&#xff1a; 总体框架 1.创建pom.xml依赖 <!--打包方式--><packaging>war</packaging><!--依赖--><dependencies><dependency><groupId>org.s…...

《Windows API每日一练》4.1 GDI绘图

本节必须掌握的知识点&#xff1a; GDI原理 GDI函数调用 GDI基本图形 4.1.1 GDI原理 GDI&#xff0c;全称是Graphics Device Interface&#xff08;图形设备接口&#xff09;&#xff0c;是微软Windows操作系统中提供的一套用于渲染图形和格式化文本的API&#xff08;应用程序…...

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 关键字的作用&#xff1a; 主要作用在于 控制变量或函数的作用域、生命周期以及它们如何被不同部分的程序访问&#xff0c;从而帮助程序员管理内存、避免命名冲突&#xff0c;并实现特定的设计模式&#xff08;如单例模式&#xff09;。 1. 静态局部变量&#xff1a;当…...

基于文本和图片输入的3D数字人化身生成技术解析

随着虚拟现实、增强现实和元宇宙等技术的飞速发展,对高度逼真且具有表现力的3D数字人化身的需求日益增长。传统的3D数字人生成方法往往需要依赖大量的3D数据集,这不仅增加了数据收集和处理的成本,还限制了生成的多样性和灵活性。为了克服这些挑战,我们提出了一种基于文本提…...

C语言 | Leetcode C语言题解之第150题逆波兰表达式求值

题目&#xff1a; 题解&#xff1a; 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安全性的重要性及实施策略

在当今日益互联的世界中&#xff0c;API&#xff08;应用程序编程接口&#xff09;成为连接不同软件系统的关键桥梁。随着API的使用越来越广泛&#xff0c;其安全性问题也日益凸显。一个不安全的API可能会使企业数据和用户信息面临严重的风险。因此&#xff0c;确保API的安全性…...

现在Java行情不好可以转.net吗?

转向.NET开发可能是一个选择&#xff0c;但要注意以下几点。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频 讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我在后台发给你。 技术转…...

大文件word生成的处理与解决策略

前言 对于简单word文档的生成导出&#xff0c;java已经有着很多技术来进行处理&#xff0c;在有着相对固定的格式样板下&#xff0c;采用word模板导出相对会是比较好的选择。但是当数据量且包含大量图片后&#xff0c;采用模板导出就显得无力了&#xff0c;模板的缺点是无法应…...

unity3d:GameFramework+xLua+Protobuf+lua-protobuf,与服务器交互收发协议

概述 1.cs收发协议&#xff0c;通过protobuf序列化 2.lua收发协议&#xff0c;通过lua-protobuf序列化 一条协议字节流组成 C#协议基类 CSPacketBase&#xff0c;SCPacketBaseC#用协议基类 proto生成的CS类&#xff0c;基于这两个基类。分别为CSPacketBase是客户端发送至服…...

二刷算法训练营Day30 | 回溯算法(6/6)

目录 详细布置&#xff1a; 1. 回溯总结 2. 332. 重新安排行程 3. 51. N 皇后 4. 37. 解数独 详细布置&#xff1a; 1. 回溯总结 回溯是递归的副产品&#xff0c;只要有递归就会有回溯&#xff0c;所以回溯法也经常和二叉树遍历&#xff0c;深度优先搜索混在一起&#x…...

【车载AI音视频电脑】200万像素迷你一体机

产品主要特点&#xff1a; -设备安装方便简洁&#xff0c;可通过3M胶直接将设备粘 贴到车前挡风玻璃上 -支持IE预览&#xff0c;手机&#xff0c;PAD实时预览&#xff0c; 支持电脑客 户端实时预览功能 -内置2路模拟高清, 每路均可达到200万像素。另 外可扩充2路1080P模拟…...

齐普夫定律在循环神经网络中的语言模型的应用

目录 齐普夫定律解释公式解释图与公式的关系代码与图的分析结论 使用对数表达方式的原因1. 线性化非线性关系2. 方便数据可视化和分析3. 降低数值范围4. 方便参数估计公式详细解释结论 来自&#xff1a;https://zh-v2.d2l.ai/chapter_recurrent-neural-networks/language-model…...

如何在Android Studio上发布Flutter应用

发布Flutter应用到Android平台是一个多步骤的过程&#xff0c;涉及配置应用、生成签名密钥、配置Gradle文件、构建发布版本APK等步骤。本文将详细介绍这些步骤&#xff0c;帮助你顺利发布Flutter应用。 1. 准备你的应用 在发布之前&#xff0c;确保你的应用在开发环境中运行良…...

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前端不挂科&#xff1a;深入探索与实战指南 在数字化时代的浪潮中&#xff0c;Web前端开发已成为一项炙手可热的技能。然而&#xff0c;对于许多初学者来说&#xff0c;如何避免在Web前端课程中挂科却成为了一道难题。本文将从四个方面、五个方面、六个方面和七个方面&…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

学校招生小程序源码介绍

基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码&#xff0c;专为学校招生场景量身打造&#xff0c;功能实用且操作便捷。 从技术架构来看&#xff0c;ThinkPHP提供稳定可靠的后台服务&#xff0c;FastAdmin加速开发流程&#xff0c;UniApp则保障小程序在多端有良好的兼…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...