观察者模式 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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hands on RL 之 Deep Deterministic Policy Gradient(DDPG)

Hands on RL 之 Deep Deterministic Policy Gradient&#xff08;DDPG&#xff09; 文章目录 Hands on RL 之 Deep Deterministic Policy Gradient&#xff08;DDPG&#xff09;1. 理论部分1.1 回顾 Deterministic Policy Gradient(DPG)1.2 Neural Network Difference1.3 Why i…

Elisp之获取PC电池状态(二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

SAP Fiori 问题收集

事务代码篇 启动工作台&#xff1a;/N/UI2/FLP 错误日志&#xff1a; /n/IWFND/ERROR_LOG 服务清单&#xff1a; /n/IWFND/MAINT_SERVICE 创建语义对象&#xff1a;/N/UI2/SEMOBJ 创建目录&#xff1a;/N/UI2/FLPD_CONF&#xff08;cross-client&#xff09;或 /N/UI2…

OpenCV-Python中的图像处理-视频分析

OpenCV-Python中的图像处理-视频分析 视频分析Meanshift算法Camshift算法光流Lucas-Kanade Optical FlowDense Optical Flow 视频分析 学习使用 Meanshift 和 Camshift 算法在视频中找到并跟踪目标对象: Meanshift算法 Meanshift 算法的基本原理是和很简单的。假设我们有一堆…

Spring Security用户授权

用户认证在上一篇用户认证 用户授权 总体流程&#xff1a; 在SpringSecurity中&#xff0c;会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication&#xff0c;然后获取其中的权限信息。…

idea中Maven报错Unable to import maven project: See logs for details问题的解决方法

idea中Maven报错Unable to import maven project: See logs for details问题的解决方法。 在查看maven的环境配置和idea的maven配置后&#xff0c;发现是idea 2020版本和maven 3.9.3版本的兼容性问题。在更改为Idea自带的maven 3.6.1版本后问题解决&#xff0c;能成功下载jar包…

word 应用 打不开 显示一直是正在启动中

word打开来显示一直正在启动中&#xff0c;其他调用word的应用也打不开&#xff0c;网上查了下以后进程关闭spoolsv.exe,就可以正常打开word了

AI绘画 | 一文学会Midjourney绘画,创作自己的AI作品(快速入门+参数介绍)

一、生成第一个AI图片 首先&#xff0c;生成将中文描述词翻译成英文 然后在输入端输入&#xff1a;/imagine prompt:Bravely running boy in Q version, cute head portrait 最后&#xff0c;稍等一会即可输出效果 说明&#xff1a; 下面的U1、U2、U3、U4代表的第一张、第二张…

AI Chat 设计模式:15. 桥接模式

本文是该系列的第十五篇&#xff0c;采用问答式的方式展开&#xff0c;问题由我提出&#xff0c;答案由 Chat AI 作出&#xff0c;灰色背景的文字则主要是我的一些思考和补充。 问题列表 Q.1 如果你是第一次接触桥接模式&#xff0c;那么你会有哪些疑问呢&#xff1f;A.1Q.2 什…

JavaScript(JavaEE初阶系列13)

目录 前言&#xff1a; 1.初识JavaScript 2.JavaScript的书写形式 2.1行内式 2.2内嵌式 2.3外部式 2.4注释 2.5输入输出 3.语法 3.1变量的使用 3.2基本数据类型 3.3运算符 3.4条件语句 3.5循环语句 3.6数组 3.7函数 3.8对象 3.8.1 对象的创建 4.案例演示 4…

广告ROI可洞察到订单转化率啦

toB广告营销人的一日三问&#xff1a; 如何实现线索增长&#xff1f;如何获取更多高质量线索&#xff1f;如何能用更少的钱拿到更多高质量的线索&#xff1f; < 广告营销的终极目标&#xff0c;就是提升ROI > 从ROI公式中&#xff0c;可以找到提升广告营销ROI的路径&…

爬楼梯(一次爬1或2层)

一&#xff0c;题目描述 二&#xff0c;解题思路 动态规划 动规五部曲&#xff1a; 1. 确认dp数组以及下标含义 2. 推导递推公式 3. 确认dp数组如何初始化 4. 确认遍历顺序 5. 打印dp数组 dp数组含义&#xff1a;到第i层的方法数目 下标含义&#xff1a;层数 递推公式&…