设计模式 —— 观察者模式

news2024/12/27 12:14:58

设计模式 —— 观察者模式

  • 什么是观察者模式
      • 观察者模式定义
      • 观察者模式的角色
      • 观察者模式的使用场景
      • 观察者模式的实现
  • 被观察者(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:
    // 构造函数,初始化怪物的血量为10
    Monster() : _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:
    // 默认构造函数,设置初始血量为100
    Monster() :_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; // 怪物的气势,默认为0
    std::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.coloradmin.cn/o/1809839.html

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

相关文章

Webpack 从入门到精通-基础篇

一、webpack 简介 1.1 webpack 是什么 webpack 是一种前端资源构建工具&#xff0c;一个静态模块打包器(module bundler)。 在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。 它将根据模块的依赖关系进行静态分析&#xff0c;打包生成对应的…

MYSQL六、存储引擎的认识

一、存储引擎 1、MySQL体系结构 连接层&#xff1a;最上层是一些客户端和链接服务&#xff0c;包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念&#xff0c;为…

Open vSwitch 数据包接收的实现

一、Open vSwitch 数据包的来源 Open vSwitch 中的数据包有许多种来源&#xff1a; 物理网络接口&#xff1a;OVS 可以连接到物理网络设备&#xff0c;并处理从这些设备收到的数据包。这些数据包可能来自外部网络&#xff0c;需要被转发或进一步处理。虚拟网络接口&#xff1a…

MySQL 常见客户端程序

本篇主要介绍MySQL常见的客户端程序 目录 一、mysqlcheck 二、mysqldump 三、mysqladmin 四、mysqldumpslow 五、mysqlbinlog 六、mysqlshow 显示列的具体信息​编辑 七、mysqlslap 一、mysqlcheck mysqlcheck是MySQL的表维护程序&#xff0c;其功能主要包含以下四个方…

遗传算法笔记:基本工作流程

1 介绍 遗传算法有5个主要任务&#xff0c;直到找到最终的解决方案 2 举例 2.1 问题描述 比如我们有 5 个变量和约束&#xff0c;其中 X1、X2、X3、X4 和 X5 是非负整数且小于 10&#xff08;0、1、2、4、5、6、7、8、9&#xff09;我们希望找到 X1、X2、X3、X4 和 X5 的最…

01 Linux网络设置

目录 1.1 查看及测试网络 1.1.1 查看网络配置 1. 查看网络接口地址 1. 查看活动的网络接口设备 2. 查看指定的网络接口信息 2. 查看主机名称 3. 查看路由表条目 4. 查看网络连接情况 1.1.2 测试网络连接 1. 测试网络连通性 2. 跟踪数据包的路由途径 3. 测试DNS域名解析 1.2 设…

Apache ShardingSphere实战与核心源码剖析

Apache ShardingSphere实战与核心源码剖析 1.数据库架构演变与分库分表介绍 1.1 海量数据存储问题及解决方案 如今随着互联网的发展,数据的量级也是成指数的增长,从GB到TB到PB。对数据的各种操作也是愈加的困难,传统的关系性数据库已经无法满足快速查询与插入数据的需求。…

HTML LocalStorage

一篇关于HTML本地存储的文章 Window.localStorage 只读的localStorage 属性允许你访问一个Document 源&#xff08;origin&#xff09;的对象 Storage&#xff1b;存储的数据将保存在浏览器会话中。 localStorage 类似 sessionStorage&#xff0c;但其区别在于&#xff1a;存储…

AXI_GPIO

REVIEW 关于PS端已经学习过&#xff1a; zynq PS端 GPIO-CSDN博客 zynq PS点灯-CSDN博客 C基础与SDK调试方法-CSDN博客 Zynq上GPIO无论是MIO还是EMIO&#xff0c;都是属于PS侧的资源&#xff0c;相当于是硬核。 而作为一个PS与PL相互协作的平台&#xff0c;当PS侧的GPIO硬核不…

使用opencv在图像上画带刻度线的对角线,以图像中心点为0点

使用OpenCV在图像上绘制带刻度线的对角线&#xff0c;可以通过以下步骤实现。我们将首先找到图像的中心点&#xff0c;然后绘制对角线线&#xff0c;并在这些线的适当位置绘制刻度线。以下是详细的C代码示例&#xff1a; void Draw_diagonal(cv::Mat& mat, double dFactor…

【Python教程】4-字符串、列表、字典、元组与集合操作

在整理自己的笔记的时候发现了当年学习python时候整理的笔记&#xff0c;稍微整理一下&#xff0c;分享出来&#xff0c;方便记录和查看吧。个人觉得如果想简单了解一名语言或者技术&#xff0c;最简单的方式就是通过菜鸟教程去学习一下。今后会从python开始重新更新&#xff0…

shell编程(四)—— 运算符

和其他编程语言一样&#xff0c;bash也有多种类型的运算符&#xff0c;本篇对bash的相关运算符做简单介绍。 一、运算符 1.1 算术运算符 常见的算术运算符&#xff0c;如加&#xff08;&#xff09;、减&#xff08;-&#xff09;、乘&#xff08;*&#xff09;、除&#xf…

Qt安装时出现无法下载存档,环境配置,main中自定义类编译不过问题

1. Qt安装时出现无法下载存档 进入Qt安装程序exe所在的文件目录&#xff0c;一般在下载文件夹&#xff0c;右键打开cmd。cmd输入&#xff1a;对应的exe镜像提速。 .\qt-online-installer-windows-x64-4.8.0.exe --mirror https://mirrors.cloud.tencent.com/qt/ 2. 环境配置 …

统计信号处理基础 习题解答10-11

题目 我们希望根据一个人的身高来估计他的体重。为了判断其可行性,对N100个人取数据&#xff0c;产生有序的数据对(h,w),其中h代表身高,w代表体重。得到的数据如图10.9(a)所示的。解释你如何利用MMSE估计量根据一个人的身高来猜测他的体重。对于这些数据的建模有些什么样的假设…

6、后端项目初始化

打开idea后&#xff0c; New Project &#xff0c;用Maven构建 Spring Boot 项目 点击Next后&#xff1a;先勾选两个基本的依赖&#xff0c;后面再手动添加其它需要的依赖 Spring Web: 表示是一个web应用程序 Lombok&#xff1a;写实体类的时候添加Data注解后就会自动加上g…

npm install 的原理

1. 执行命令发生了什么 &#xff1f; 执行命令后&#xff0c;会将安装相关的依赖&#xff0c;依赖会存放在根目录的node_modules下&#xff0c;默认采用扁平化的方式安装&#xff0c;排序规则为&#xff1a;bin文件夹为第一个&#xff0c;然后是开头系列的文件夹&#xff0c;后…

关于头条项目经验面试题的总结

文章目录 前言一、论坛项目经典话术二、请你介绍一下你最近的项目吧2.1 话术1 三、你的公司的开发环境是怎么搭建的&#xff1f;四、登录你们是怎么做的&#xff1f;4.1 账号密码登录4.2 手机验证码发送4.2.1 手机验证码发送4.2.2 手机验证码登录 五、用户行为限流是怎么做的&a…

Java面向对象-方法的重写、super

Java面向对象-方法的重写、super 一、方法的重写二、super关键字1、super可以省略2、super不可以省略3、super修饰构造器4、继承条件下构造方法的执行过程 一、方法的重写 1、发生在子类和父类中&#xff0c;当子类对父类提供的方法不满意的时候&#xff0c;要对父类的方法进行…

1000道互联网大厂面试题:ZooKeeper+Dubbo+Spring+MySQL等(含答案)

然后存储回内存&#xff0c;这个过程可能会出现多个线程交差。 24、a a b 与 a b 的区别 25、我能在不进行强制转换的情况下将一个 double 值赋值给 long类型的变量吗&#xff1f; 26、3*0.1 0.3 将会返回什么&#xff1f;true 还是 false&#xff1f; 27、int 和 Inte…

C语言学生管理系统

整理U盘发现一个C语言写的学生管理系统&#xff0c;全部代码放放我上传的资源里了 #include<stdio.h> #include<stdlib.h> #include<string.h>//需要用到strcmp函数#define LEN 15//姓名和学号的最大字符数 #define N 50//最大学生人数int n 0, t 1;//n代表…