观察者模式的理解和引用

1.前言

在之前的H5小游戏中,对于长连接发送的不同类型数据包的处理,是通过switch语句进行处理的,于是在自己的代码中出现了大量的case分支,不方便进行维护和后期的版本迭代。于是在老师的指导下,开始寻求使用观察者模式来解决case分支过多、代码冗余的问题。

H5小游戏介绍和代码仓库:基于WebSocket通信的H5小游戏总结-CSDN博客

2.旧代码

		//在信息中枢处根据消息类型进行特定的处理switch requestPkg.Type {case pojo.CertificationType://用户认证client.CertificationProcess(requestPkg)case pojo.CreateRoomType://创建房间号,并将创建者加入房间client.CreateRoomProcess()case pojo.JoinRoomType://1.加入房间的前提,先建立连接//2.完成用户认证//3.发送消息类型和房间号 Type uuid//只有完成上述步骤,才可以加入房间var data map[string]interface{}err = json.Unmarshal([]byte(requestPkg.Data), &data)if err != nil {fmt.Println("解析 JSON 失败:", err)return}uuidValue, ok := data["uuid"].(string)if !ok {fmt.Println("uuid 字段不存在或不是字符串类型")return}client.JoinRoomProcess(uuidValue)case pojo.RefreshScoreType://什么是否进行分数更新,前端判断 type:RefreshScoreType, data:step、step、score//当用户的行为触发前端游戏机制的更新时,前端调用此接口,后端进行分数的转发 不需要做业务处理,直接转发即可fmt.Println("游戏交换中数据", client)client.RefreshScoreProcess(requestPkg)case pojo.DiscontinueQuitType:client.DiscontinueQuitProcess()case pojo.GameOverType://游戏结束类型好像没有太大用,游戏结束的时候的提醒,通过分数更新就可以实现了fmt.Println("GameOverType")case pojo.HeartCheckType://开启一个协程遍历hub中的Client,进行健康检测,生命时间是否会过期,如果过期进行逻辑删除和关闭连接if requestPkg.Data == "PING" {client.HeartCheckProcess()}}

3.观察者模式的引入

观察者模式是使用频率最高的设计模式之一,用于建立对象与对象之间的依赖关系。一个对象发生改变时将自动通知其他对象,其他对象将响应做出反应。在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。

比如在我们的日常生活中,红灯停,绿灯行。在这句话描述的场景中,红绿灯是观察目标,即被观察者;而行人和车辆是观察者;红绿灯即观察目标的状态发生变动的时候,行人和车辆会接收到通知,调整自己的行为。这种建立一个红绿灯对象和多个行人车辆对象之间的依赖关系的模式就是观察者模式。

观察者模式结构中通常包括观察目标和观察者两个继承层次结构,具体结构如下图示意:

Subject是抽象观察目标,我们一般定义为抽象类或者接口,在里面我们规定观察目标应该具有的方法,添加观察者,删除观察者,通知观察者。

ConcreteSubject是具体观察目标,是我们抽象类或者接口的具体实现类,在里面我们定义观察目标方法的具体实现即如何添加观察者、删除观察者、通知观察者。

Observer是抽象观察者,同样地我们一般定义为抽象类或者接口,在里面我们规定观察者应该具有的方法,即观察目标发生变动后的行为,一般我们定义为Update()方法。

ConcreteObserver是具体观察对象,是我们抽象类或者接口的具体实现类,在里面我们定义观察者在观察目标的行为发生变动后,应该执行的具体逻辑代码。

4.观察者模式Demo

Demo的目录结构如下:

subject.go 这里我们定义观察目标接口,里面定义三个方法签名,添加、删除观察者和通知观察者

package subjectimport "demo/TrafficLightsAndPedestrians/observer"type Subject interface {AddPedestriansAndCars(buyer ...observer.Observer)RemovePedestriansAndCars(buyer observer.Observer)NotifyPedestriansAndCars(flag bool)
}

TrafficLights.go 观察目标的具体实现,这里我们模拟红绿灯的情景,因为是具体实现类,直接命名为TrafficLights。在这个类中我们实现了subject接口中定义的所有方法。

package implimport ("demo/TrafficLightsAndPedestrians/observer""fmt"
)type TrafficLights struct {pedestriansAndCars []observer.Observer
}func (p *TrafficLights) AddPedestriansAndCars(buyer ...observer.Observer) {p.buyers = append(p.buyers, buyer...)fmt.Println("可变参数中加入了", p.buyers)
}func (p *TrafficLights) RemovePedestriansAndCars(buyer observer.Observer) {for index, value := range p.buyers {if value == buyer {copy(p.buyers[index:], p.buyers[index+1:])p.buyers = p.buyers[:len(p.buyers)-1]fmt.Println("删除后:", p.buyers)break}}
}
func (p *TrafficLights) NotifyPedestriansAndCars(flag bool) {for _, value := range p.buyers {value.Update(flag)}
}

observer.go 观察者接口,我们定义了一个Update方法,用于更新观察者的行为,当观察目标发生变动的时候,观察者应该执行的行为。

package observertype Observer interface {Update(flag bool)
}

PedestriansAndCars.go 观察者具体方法,由于这里我们模拟的交通信号灯的情景,所以这里观察者的具体实现类直接命名为PedestriansAndCars。这里的Update方法我们实现了当红绿灯发生变动时,行人和车辆应该执行的具体行为,这里我们为了模拟情况,简单地进行打印输出操作。

package implimport "fmt"type PedestriansAndCars struct {Name string
}func (b *PedestriansAndCars) Update(flag bool) {if flag {fmt.Println("绿灯亮", b.Name, "可以走了")} else {fmt.Println("红灯亮", b.Name, "请站在原地等待")}
}

main.go 主函数的场景,在这里我们创建trafficLights观察目标对象,在观察目标中加入行人和车辆,当trafficLights观察目标发生变动的时候,会通知执行所有的已经添加到观察目标切片中的所有行人和车辆。

package mainimport (impl2 "demo/TrafficLightsAndPedestrians/observer/impl""demo/TrafficLightsAndPedestrians/subject/impl"
)func main() {trafficLights := new(impl.TrafficLights)person01 := &impl2.PedestriansAndCars{Name: "小1"}person02 := &impl2.PedestriansAndCars{Name: "小2"}person03 := &impl2.PedestriansAndCars{Name: "小3"}car01 := &impl2.PedestriansAndCars{Name: "车1"}car02 := &impl2.PedestriansAndCars{Name: "车2"}trafficLights.AddPedestriansAndCars(person01, person02, person03, car01, car02)trafficLights.NotifyPedestriansAndCars(false)
}

这里之所以采用接口调用的方式,是为了方便后期代码功能的扩展,如果我们想要在代码中再次添加一个观察目标,直接定义一个结构体去实现subject接口即可,其余代码不需要进行变动;如果·

5.改造后的新代码

本次改造主要是对websocket长连接进行更改,在原有socket包的基础上添加了subject观察目标包和observer观察者,在观察目标发生变动后,会通知所有的观察者,观察者接收到信息后,会执行对应的方法。

在这里观察目标为客户端不断发送的websocket数据包,观察者是原先switch语句下的各个分支。一旦观察目标接收到websocket数据包,就通知所有的观察者,观察者是否否执行,取决于观察者内部信息类型的判断是否符合传送数据包的类型。

subject.go

package observedimport ("klotski/pojo""klotski/socket/subscriber"
)type Observed interface {AddProcess(process subscriber.Subscriber)RemoveProcess(process subscriber.Subscriber)Notify(client *pojo.Client, request *pojo.RequestPkg)
}

controller.go

package implimport ("klotski/pojo""klotski/socket/subscriber"
)type Controller struct {processes []subscriber.Subscriber
}func (c *Controller) AddProcess(process ...subscriber.Subscriber) {c.processes = append(c.processes, process...)}func (c *Controller) RemoveProcess(process subscriber.Subscriber) {for i, o := range c.processes {if o == process {c.processes = append(c.processes[:i], c.processes[i+1:]...)break}}
}func (c *Controller) Notify(client *pojo.Client, request *pojo.RequestPkg) {for _, observer := range c.processes {observer.Update(client, request)}
}

observer.go

package subscriberimport "klotski/pojo"type Subscriber interface {Update(client *pojo.Client, request *pojo.RequestPkg)
}

process.go

package implimport ("encoding/json""fmt""klotski/pojo"
)// CertificationObserver 用户认证观察者
type CertificationObserver struct{}func (o *CertificationObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {if request.Type == pojo.CertificationType {client.CertificationProcess(*request)}
}// CreateRoomObserver 创建房间观察者
type CreateRoomObserver struct{}func (o *CreateRoomObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {if request.Type == pojo.CreateRoomType {client.CreateRoomProcess()}
}// JoinRoomObserver 加入房间观察者
type JoinRoomObserver struct{}func (o *JoinRoomObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {if request.Type == pojo.JoinRoomType {var data map[string]interface{}if err := json.Unmarshal([]byte(request.Data), &data); err != nil {fmt.Println("解析 JSON 失败:", err)}if uuidValue, ok := data["uuid"].(string); !ok {fmt.Println("uuid 字段不存在或不是字符串类型")return} else {client.JoinRoomProcess(uuidValue)}}
}// RefreshScoreObserver 刷新游戏分数观察者
type RefreshScoreObserver struct{}func (o *RefreshScoreObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {if request.Type == pojo.RefreshScoreType {client.RefreshScoreProcess(*request)}
}// DiscontinueQuitObserver 主动断开连接观察者
type DiscontinueQuitObserver struct{}func (o *DiscontinueQuitObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {if request.Type == pojo.DiscontinueQuitType {client.DiscontinueQuitProcess()}
}// GameOverObserver 游戏结束观察者
type GameOverObserver struct{}func (o *GameOverObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {if pojo.GameOverType == request.Type {fmt.Println("GameOverType")}
}// HeartCheckObserver 健康检测观察者
type HeartCheckObserver struct{}func (o *HeartCheckObserver) Update(client *pojo.Client, request *pojo.RequestPkg) {if pojo.HeartCheckType == request.Type {if request.Data == "PING" {client.HeartCheckProcess()}}
}

6.总结

1.观察者模式是一种使用频率非常高的设计模式,无论是移动应用、Web应用或者桌面应用,观察者模式几乎无处不在。它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及一对一或者一对多的对象交互场景都可以使用观察者模式。

2.要学会及时发现自己代码中的问题,并不是代码能够运行起来就可以了,而是要不断进行改进,追求优雅和简洁。

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

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

相关文章

SpingBoot集成Rabbitmq及Docker部署

文章目录 介绍RabbitMQ的特点Rabbitmq术语消息发布接收流程 Docker部署管理界面说明Overview: 这个页面显示了RabbitMQ服务器的一般信息,例如集群节点的名字、状态、运行时间等。Connections: 在这里,可以查看、管理和关闭当前所有的TCP连接。Channels: …

Unity Live Capture 中实现面部捕捉同步模型动画

Unity Face Capture 是一个强大的工具,可以帮助你快速轻松地将真实人脸表情捕捉到数字模型中。在本文中,我们将介绍如何在 Unity Face Capture 中实现面部捕捉同步模型动画。 安装 |实时捕获 |4.0.0 (unity3d.com) 安装软件插件 安装 Live Capture 软件…

Linux_网络项目_WEB服务器 处理服务器写入失败后sigpipe信号导致服务器崩溃退出问题,引入线程池缓解大量请求,服务器组件化重构,在线计算机业务测试

文章目录 1. 处理服务器写入管道出错2. 引入线程池缓解大量请求导致服务器崩溃设计线程任务类单例线程池组件设计 3.代码位置4. 在线计算机业务运行截图 1. 处理服务器写入管道出错 经过测试,服务器在读取报文时如果出错可以选择直接关闭这个TCP里链接来节省资源。…

【MySQL高级篇】08-事务篇

第13章:事务基础知识 #09-事务的基础知识#1.事务的完成过程 #步骤1:开启事务 #步骤2:一系列的DML操作 #.... #步骤3:事务结束的状态:提交的状态(COMMIT) 、 中止的状态(ROLLBACK)#2. 显式事务#2.1 如何开启? 使用关键…

项目分享--NO.1

搭建高可用的web集群.部署网站 包含数据库,ceph/nfs,haproxy,keepalived,ansible部署 1,配置ansible管理环境 创建工作目录,编写ansible配置文件,和主机清单文件,yum配置文件 将yum文件到控制机上,然后用模块上传到被管理机器上 #vim 01-upload-repo.yml --- - name: confi…

柚见十三期(优化)

前端优化 加载匹配功能与加载骨架特效 骨架屏 : vant-skeleton index.vue中 /** * 加载数据 */ const loadData async () > { let userListData; loading.value true; //心动模式 if (isMatchMode.value){ const num 10;//推荐人数 userListData await myA…

基于SpringBoot框架实现的B2B平台的医疗病历交互系统

采用技术 基于SpringBoot框架实现的B2B平台的医疗病历交互系统的设计与实现~ 开发语言:Java 数据库:MySQL 技术:SpringBootMyBatis 工具:IDEA/Ecilpse、Navicat、Maven 页面展示效果 管理员角色 医院管理 医院注册 医院文…

音频的录制及播放

在终端安装好pip install pyaudio,在pycharm中敲入录音的代码,然后点击运行可以在10s内进行录音,录音后的音频会保存在与录音代码同一路径项目中,然后再新建项目敲入播放的代码,点击运行,会把录入的录音进行…

C语言-strerror(打印错误信息)和perror(获得错误信息)

strerror(打印错误信息)和perror(获得错误信息) strerror 语法格式 返回类型是char* 都需要头文件 errno.h 这里是错误码 每一个错误码代表一个错误信息 错误码 对照的错误信息 每一种编译器在写的时候已经规定好了 错误码对…

四连杆机构运动学仿真 | 【Matlab源码+理论公式文本】

【程序简介】💻🔍 本程序通过matlab实现了四连杆机构的运动学仿真编程,动态展现了四连杆机构的运动动画,同时给出了角位移、角速度和角加速度的时程曲线,除了程序本身,还提供了机构运动学公式推导文档&…

【Hadoop大数据技术】——MapReduce分布式计算框架(学习笔记)

📖 前言:MapReduce是Hadoop系统核心组件之一,它是一种可用于大数据并行处理的计算模型、框架和平台,主要解决海量数据的计算问题,是目前分布式计算模型中应用较为广泛的一种。 目录 🕒 1. MapReduce概述&am…

Ubuntu 虚拟机安装

最小化安装后常用工具 sudo apt-get install vim# ifconfig apt install net-tools # nload apt install nload # 很多都要用到 apt install build-essential # 开发相关 apt install gcc gapt install iproute2 ntpdate tcpdump telnet traceroute \ nfs-kernel-server nfs…

Ubuntu 14.04:安装 PaddleOCR 2.3

目录 一、说明 1.1 如何选择版本 1.2 查看 github 中的 PaddleOCR 版本 二、安装 2.1 安装前环境准备 2.2 下载包 2.3 解压 2.4 安装依赖库 异常处理:Read timed out. 2.5 下载推理模型:inference 2.5.1 模型存放位置 2.5.2 模型下载链接 2.5.…

JVM学习-底层字节码的执行过程

目录 1.一个简单的程序分析 2. a,a,a--在JVM中的执行过程 3. 一个好玩的xx 4.方法调用的字节码分析、多态的实现、对象头 5. try-catch-finally的字节码分析 5.1 try-catch 5.2 try-catch-finally 5.3特殊情况 5.3.1 try和finally块中都出现了re…

【Miniconda】基于conda避免运行多个PyTorch项目时发生版本冲突

【Miniconda】基于conda避免运行多个PyTorch项目时发生版本冲突 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程👈 希望得到…

SQL的执行与优化

文章目录 MySQL查询原理与优化一、select语句的执行顺序二、join 的执行与优化1、驱动表 & 被驱动表2、Simple Nested Loop Join3、Index Nested Loop Join4、Block Nested Loop Join5、Hash Join6、join 优化小结 三、on 与 where 对比四、group by 的执行与优化1、group …

刚刚离乳的幼猫该如何选择猫粮品牌?

亲爱的猫友们,当你家的幼猫刚刚离乳,准备踏入猫粮的世界时,如何选择一款合适的猫粮品牌确实是个让人头疼的问题。🐾 别担心,今天我就来为大家推荐一款值得信赖的幼猫粮——福派斯幼猫粮。 1️⃣ 考虑幼猫的营养需求 幼…

macOS系统中通过brew安装MongoDB

Macos 修改目录权限: sudo chmod -R 777 你的文件夹 本文使用homebrew进行安装简单,因为从官网下载安装包并手动安装需要移动安装包到合适的目录下并配置环境变量等一大堆操作后才能使用数据库(若没有安装过brew请自行百度进行安装brew&am…

ArkTs的资源Resource类型怎么转为string

使用ResourceManager同步转换 请参看:ResourceManager.getStringSync9 例子: try { let testStr: string this.context.resourceManager.getStringSync($r(app.string.test).id); } catch (error) { console.error(getStringSync failed, error code…

【四 (5)数据可视化之 Pyecharts常用图表及代码实现 】

目录 文章导航一、介绍[✨ 特性]二、安装Pyecharts三、主题风格四、占比类图表1、饼图2、环形图3、玫瑰图4、玫瑰图-多图5、堆叠条形图6、百分比堆叠条形图 五、比较排序类1、条形图2、雷达图3、词云图4、漏斗图 六、趋势类图表1、折线图2、堆叠折线图3、面积图4、堆叠面积图 七…