《游戏编程模式》学习笔记(四) 观察者模式 Observer Pattern

定义

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

这是定义,看不懂就看不懂吧,我接下来举个例子慢慢说

为什么我们需要观察者模式

我们看一个很简单的需求,现在要你在游戏中加入成就系统,在物体坠落1000米的时候给玩家发一个成就勋章,你要这么做?

最直观的方法就是,在游戏的物理系统那一部分中,加入这么一段代码:

void Physics::updateEntity(Entity& entity)
{
    bool wasOnSurface = entity.isOnSurface();
    entity.accelerate(GRAVITY);
    entity.update();
    if (wasOnSurface && !entity.isOnSurface())
    {
        if (surface.height - entity.height > 1000)
        {
            //解锁成就
            unlockFallOffBridge();
        }
    }
}

咋一看是不是还行?就加了几行而已。
那么如果我还要求你播放坠落音效呢?是不是还得这样写:

void Physics::updateEntity(Entity& entity)
{
    bool wasOnSurface = entity.isOnSurface();
    entity.accelerate(GRAVITY);
    entity.update();
    if (wasOnSurface && !entity.isOnSurface())
    {
        if (surface.height - entity.height > 1000)
        {
            //解锁成就
            unlockFallOffBridge();
            
            //播放音效
            playfallmusic();
        }
    }
}

这样看也还行,那如果组长让你根据物体撞击不同的地面,播放不同的地面音效,那这段代码是不是又得膨胀了:

void Physics::updateEntity(Entity& entity)
{
    bool wasOnSurface = entity.isOnSurface();
    entity.accelerate(GRAVITY);
    entity.update();
    if (wasOnSurface && !entity.isOnSurface())
    {
        if (surface.height - entity.height > 1000)
        {
            //解锁成就
            unlockFallOffBridge();
            
            //播放音效
            if (hitground)
            {
                playhitgroundmusic();
            }
            if (hitwater)
            {
                playhitwatermusic();
            }
            //.....
        }
    }
}

要知道,这可是在你的游戏的物理引擎中,我们并不想看到在处理撞击代码的线性代数时, 有出现关于成就系统,音效系统的调用是不?我们喜欢的是,照旧,让关注游戏一部分的所有代码集成到一块。我们想要解耦物理系统和这些不相关的东西。

这就是观察者模式出现的原因。 这让代码宣称有趣的事情发生了,而不必关心到底是谁接受了通知。

一旦你使用了观察者模式,你的代码就会变成这样:

void Physics::updateEntity(Entity& entity)
{
    bool wasOnSurface = entity.isOnSurface();
    entity.accelerate(GRAVITY);
    entity.update();
    if (wasOnSurface && !entity.isOnSurface())
    {
        notify(entity, EVENT_START_FALL);
    }
}

是不是简洁了很多很多?比刚才那一大堆丑陋的代码好看多了。

观察者模式做的就是声称,“额,我不知道有谁感兴趣,但是这个东西刚刚掉下去了。做你想做的事吧。”

可能有人会说,诶,这也没有完全解耦啊。的确,物理引擎确实决定了要发送什么通知,所以这并没有完全解耦。但在架构这个领域,通常只能让系统变得更好,而不是完美。

如何构建观察者模式?

最传统的构建方式就是这样,使用对象模式构建观察者

我们先写一个基础的观察者抽象基类

class Observer
{
public:
    virtual ~Observer() {}
    virtual void onNotify(const Entity& entity, Event event) = 0;
};

然后让我们的成就系统和音效系统等想成为观察者的系统都继承这个基类:

class Achievements : public Observer
{
public:
    virtual void onNotify(const Entity& entity, Event event)
    {
        switch (event)
        {
        case EVENT_ENTITY_FELL:
            if (entity.isHero() && heroIsOnBridge_)
            {
                unlock(ACHIEVEMENT_FELL_OFF_BRIDGE);
            }
            break;

            // 处理其他事件,更新heroIsOnBridge_变量……
        }
    }


private:
    void unlock(Achievement achievement)
    {
        // 如果还没有解锁,那就解锁成就……
    }

    bool heroIsOnBridge_;
};

对于被观察者,如物理系统中,我们只要让它持有这个observer的指针就好了,一旦出现了某些事件,我们就给这些指针指向的observer发消息。
为了正式一点,让所有可能的系统都成为被观察者,我们写一个叫subject的基类,让所有想成为被观察者的系统都可以继承这个基类来成为被观察者。

class Subject
{
public:
  void addObserver(Observer* observer)
  {
    // 添加到数组中……
  }

  void removeObserver(Observer* observer)
  {
    // 从数组中移除……
  }

  void removeObserver(Observer* observer)
  {
    // 从数组中移除……
  }
protected:
  void notify(const Entity& entity, Event event)
  {
    for (int i = 0; i < numObservers_; i++)
    {
      observers_[i]->onNotify(entity, event);
    }
  }

private:
  Observer* observers_[MAX_OBSERVERS];
  int numObservers_;
};

我们可以看见,这里写了一个观察者数组,存了许多观察者的指针,这是因为大部分情况下,被观察者可能会有好多个观察者观察着它。然后我们也写了一些方法来增删这个数组。

然后就是面向对象的东西了,我们让物理系统继承这个基类

class Physics : public Subject
{
public:
  void updateEntity(Entity& entity);
};

现在,当物理引擎做了些值得关注的事情,它调用notify(),就像之前的例子。 它遍历了观察者列表,通知所有观察者。
在这里插入图片描述
恭喜你已经掌握了如何写一个观察者模式,你所看到的就是一个观察者模式的全部。现在来回顾一下定义:

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

是不是有点明白了?

**

观察者模式的使用场合

**

当一个抽象模式有两个方面,其中一个方面依赖于另一个方面,需要将这两个方面分别封装到独立的对象中,彼此独立地改变和复用的时候。
当一个系统中一个对象的改变需要同时改变其他对象内容,但是又不知道待改变的对象到底有多少个的时候。
当一个对象的改变必须通知其他对象作出相应的变化,但是不能确定通知的对象是谁的时候。

观察者模式的缺点:

  1. 由于观察者模式调用了一些虚方法,终究会比静态调用慢一些。
  2. 观察者模式是同步的。 被观察者直接调用了观察者,这意味着直到所有观察者的通知方法返回后, 被观察者才会继续自己的工作。观察者会阻塞被观察者的运行。
  3. 由于被观察者维护了一个数组来存储观察者指针,在实际情况中一般会用动态数组而不是这次例子中的静态数组。这样就会做出太多的动态分配。解决方法还是有的,那就是使用链表而不是数组来存储观察者指针(反正你都得遍历发通知,这俩差不多)。

原文链接:https://gpp.tkchu.me/observer.html

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spyglass 脚本

Spyglass小白可以使用Gui界面进行debug Spyglass检查(一)作为IC设计人员&#xff0c;熟练掌握数字前端语法检查工具Spyglass的重要性不言而喻&#xff0c;本文讲解景芯SoChttps://mp.weixin.qq.com/s/fp07o-AvaQvLT79Di0Tb7A 熟悉电路和软件之后可以使用脚本完成整个流程 sp…

IDEA全局设置MyBatis中写SQL语句提示

第一步&#xff1a;把这两个设置改成MySQL即可&#xff1a; 第二步&#xff1a;找到设置>编辑器>语言注入>店家加号&#xff0c;选择MySQL

Flink CDC系列之:TiDB CDC 导入 Elasticsearch

Flink CDC系列之&#xff1a;TiDB CDC 导入 Elasticsearch 一、通过docker 来启动 TiDB 集群二、下载 Flink 和所需要的依赖包三、在TiDB数据库中创建表和准备数据四、启动Flink 集群&#xff0c;再启动 SQL CLI五、在 Flink SQL CLI 中使用 Flink DDL 创建表六、Kibana查看Ela…

Matplotlib绘图知识小结--Python数据分析学习

一、Pyplot子库绘制2D图表 1、Matplotlib Pyplot Pyplot 是 Matplotlib 的子库&#xff0c;提供了和 MATLAB 类似的绘图 API。 Pyplot 是常用的绘图模块&#xff0c;能很方便让用户绘制 2D 图表。 Pyplot 包含一系列绘图函数的相关函数&#xff0c;每个函数会对当前的图像进行…

经典人体模型SMPL介绍(一)

SMPL是马普所提出的经典人体模型&#xff0c;目前已成为姿态估计、人体重建等领域必不可少的基础先验。SMPL基于蒙皮和BlendShape实现&#xff0c;从数千个三维人体扫描结果得来&#xff0c;后通过PCA统计学习得来。 论文&#xff1a;SMPL: A Skinned Multi-Person Linear Mode…

OpenStack监控工具

OpenStack是一个开源的云计算管理平台项目&#xff0c;是一系列软件开源项目的组合。由NASA和Rackspace合作研发并发起&#xff0c;以Apache许可证&#xff08;Apache软件基金会发布的一个自由软件许可证&#xff09;授权。 OpenStack为私有云和公有云提供可扩展的弹性的云计算…

二.net core 自动化发布到docker (Jenkins安装之后向导)

目录 ​​​​​​​​​​​​​​ 参考资料&#xff1a;https://www.jenkins.io/doc/book/installing/docker/#setup-wizard Post-installation setup wizard.(安装后安装向导) 基于上一篇文章安装&#xff0c;在安装并运行Jenkins&#xff08;不包括使用Jenkins Opera…

学习pytorch 3 tensorboard的使用

tensorboard的使用 1. 安装2. add_scalar 查看函数图形3. 查看结果4. add_image() 查看训练步骤中间结果的图片 1. 安装 pytorch conda环境 pip install tensorboard pip install opencv-python2. add_scalar 查看函数图形 常用来查看 train val loss等函数图形 from torch…

TCP 协议十大相关特性总结

目录 一、TCP特性 二、报文格式 TCP十大核心特性 1. 确认应答 2. 超时重传 3. 连接管理(三次握手,四次挥手) 三次握手 四次挥手 4. 滑动窗口 情况一:接收方的ACK丢失 情况二:发送方的数据包丢失 5. 流量控制 6. 拥塞控制 7. 延迟应答 8. 捎带应答 9. 字节流粘包问题 10. TCP的…

WebStorm修改默认打开的浏览器

有两种方式第一种修改系统默认浏览器 我采用的是下面这种&#xff0c;在webstorm中修改 将浏览器设置为默认的浏览器即可

【学会动态规划】乘积为正数的最长子数组长度(21)

目录 动态规划怎么学&#xff1f; 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后&#xff1a; 动态规划怎么学&#xff1f; 学习一个算法没有捷径&#xff0c;更何况是学习动态规划&#xff0c; 跟我…

实现文件的拖放功能

文章目录 实现文件的拖放功能1 拖放文件至QT窗口1.1 实现方法1.2 效果演示 2 拖放文件至python脚本2.1 实现方法2.2 效果演示 实现文件的拖放功能 试想一下&#xff0c;我们希望将一个python项目文件夹或者脚本在IDE中打开&#xff0c;无论是去IDE中选择文件夹路径&#xff0c;…

vuex学习总结

一、vuex工作原理 工作流程&#xff1a;需求&#xff1a;改变组件count的sun变量的值&#xff0c;先调用dispatch函数传入jia函数和要改变的值给actions&#xff08;这个actions里面必须有jia这个函数&#xff09;&#xff1b;actions收到后调用commit函数将jia方法和值传给mut…

【Visual Studio Code】--- Win11 C盘爆满 修改 Code 插件数据和缓存的保存路径

Win11 C盘爆满 修改 Code 插件数据和缓存的保存路径 一、概述二、修改 Code 插件数据和缓存的保存路径 一、概述 一个好的文章能够帮助开发者完成更便捷、更快速的开发。书山有路勤为径&#xff0c;学海无涯苦作舟。我是秋知叶i、期望每一个阅读了我的文章的开发者都能够有所成…

28 | Boss直聘数据分析

针对boss直聘网的招聘信息,然后分析互联网发展排名前十的城市在互联网方面职位的薪水,学历要求,经验要求,等等信息。 准备从以下几个方面进行分析: (1)各个城市的平均工资 (2)各个学历的平均工资 (3)各个岗位的平均工资 (4)不同工作经验要求的工资 (5)各个经验…

冉冉升起的星火,再度升级迎来2.0时代!

文章目录 前言权威性评测结果 星火大模型多模态功能插件功能简历生成文档问答PPT生成 代码能力 福利 前言 前几天从技术群里看到大家都在谈论《人工智能大模型体验报告2.0》里边的内容&#xff0c;抱着好奇和学习的态度把报告看了一遍。看完之后瞬间被里边提到的科大讯飞的星火…

(leecode)错误的集合

最近听到的&#xff0c;还可以&#xff0c;试试吧~ 题目&#xff1a; 示例&#xff1a; 提示&#xff1a; 题解&#xff1a; 思路&#xff1a; 将数字大小的位置&#xff0c;然后遍历每个位置&#xff0c;大小为0的是缺失数字&#xff0c;大小为2的是重复数字 int* findErro…

Gitlab-第四天-CD到k8s集群的坑

一、.gitlab-ci.yml #CD到k8s集群的 stages: - deploy-test build-image-deploy-test: stage: deploy-test image: bitnami/kubectl:latest # 使用一个包含 kubectl 工具的镜像 tags: - k8s script: - ls -al - kubectl apply -f deployment.yaml # 根据实际情况替换…

Linux学习之firewallD

systemctl status firewalld.service查看一下firewalld服务的状态&#xff0c;发现状态是inactive (dead)。 systemctl start firewalld.service启动firewalld&#xff0c;systemctl status firewalld.service查看一下firewalld服务的状态&#xff0c;发现状态是active (runni…

【Apollo】阿波罗自动驾驶:塑造自动驾驶技术的未来

前言 Apollo (阿波罗)是一个开放的、完整的、安全的平台&#xff0c;将帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统&#xff0c;快速搭建一套属于自己的自动驾驶系统。 开放能力、共享资源、加速创新、持续共赢是 Apollo 开放平台的口号。百度把自己所拥有的强大、…