观察者模式的理解和引用

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.rhkb.cn/news/277549.html

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

相关文章

2.26回顾章节主体线索脉络,课程要求(评分)

3)翻译程序、汇编程序、编译程序、解释程序有什么差别?各自的特性是什么? 翻译程序是指把高级语言源程序翻译成机器语言程序(目标代码)的软件。 翻译程序有两种:一种是编译程序,它将高级语言源程序一次全部…

idea Springboot 在线考试管理系统开发mysql数据库web结构java编程计算机网页

一、源码特点 springboot 在线考试管理系统是一套完善的完整信息系统,结合mvc框架和bootstrap完成本系统springboot spring mybatis ,对理解JSP java编程开发语言有帮助系统采用springboot框架(MVC模式开发),系统具有…

【ESP32接入国产大模型之MiniMax】

1. MiniMax 讲解视频: ESP32接入语言大模型之MiniMax MM智能助理是一款由MiniMax自研的,没有调用其他产品的接口的大型语言模型。MiniMax是一家中国科技公司,一直致力于进行大模型相关的研究。 随着人工智能技术的不断发展,自然语…

AI - 支持向量机算法

🧨概念 支持向量机(Support Vector Machine, SVM)是一种强大的机器学习算法,主要用于解决二分类问题。 SVM的核心思想是找到一个超平面,这个超平面能够最好地将数据分为两类,即在保证分类准确的情况下&am…

C/C++火柴棍等式

有n根(n<24)火柴棍&#xff0c;你可以拼出多少个形如“ABC"的等式?等式中的A、B、C是用火柴棍拼出的整数(若该数非零&#xff0c;则最高位不能是0)。用火柴棍拼数字0-9的拼法如图所示: 依次需要用到的火柴棍数目为6 2 5 5 4 5 6 3 7 6 。 如果是初学者可能会这么写。 …

音频的录制及播放

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

PHP+golang开源办公系统CRM管理系统

基于ThinkPHP6 Layui MySQL的企业办公系统。集成系统设置、人事管理、消息管理、审批管理、日常办公、客户管理、合同管理、项目管理、财务管理、电销接口集成、在线签章等模块。系统简约&#xff0c;易于功能扩展&#xff0c;方便二次开发。 服务器运行环境要求 PHP > 7.…

32程序结构学习

初始化代码 void LED_GPIO_Config(void) { /*定义一个GPIO_InitTypeDef类型的结构体*/GPIO_InitTypeDef GPIO_InitStructure;/*开启GPIOC的外设时钟*/RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE); /*选择要控制的GPIOC引脚*/ GPIO_InitStructur…

【管理咨询宝藏40】内部报告:电力市场建设现状、挑战及思考

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏40】内部报告&#xff1a;电力市场建设现状、挑战及思考 【格式】PDF版本 【关键词】战略规划、行业分析、管理咨询 【文件核心观点】 - 各类主…

App的测试,和传统软件测试有哪些区别?增加哪些方面的测试用例

从上图可知&#xff0c;测试人员所测项目占比中&#xff0c;App测试占比是最高的。 这就意味着学习期间&#xff0c;我们要花最多的精力去学App的各类测试。也意味着我们找工作前&#xff0c;就得知道&#xff0c;App的测试点是什么&#xff0c;App功能我们得会测试&#xff0…

人工智能迷惑行为大赏——需求与科技的较量

目录 前言 一、 机器行为学 二、人工智能迷惑行为的现象 三、产生迷惑行为的技术原因 四、社会影响分析 五、解决措施 总结 前言 随着ChatGPT热度的攀升&#xff0c;越来越多的公司也相继推出了自己的AI大模型&#xff0c;如文心一言、通义千问等。各大应用也开始内置…

数据预处理|数据清洗|使用Pandas进行异常值清洗

数据预处理|数据清洗|使用Pandas进行异常值清洗 使用Pandas进行异常值清洗1. 异常值检测1.1 简单统计分析1.2 散点图方法1.3 3σ原则1.4 箱线图 2. 异常值处理2.1 直接删除2.2 视为缺失值2.3 平均值修正2.4 盖帽法2.5 分箱平滑法2.6 回归插补2.7 多重插补2.8 不处理 使用Pandas…

论文浅尝 | GPT-RE:基于大语言模型针对关系抽取的上下文学习

笔记整理&#xff1a;张廉臣&#xff0c;东南大学硕士&#xff0c;研究方向为自然语言处理、信息抽取 链接&#xff1a;https://arxiv.org/pdf/2305.02105.pdf 1、动机 在很多自然语言处理任务中&#xff0c;上下文学习的性能已经媲美甚至超过了全资源微调的方法。但是&#xf…

【解读】保障软件供应链安全:SBOM推荐实践指南(含指南获取链接)

2023年11底&#xff0c;美国NSA&#xff08;National Security Agency&#xff09;、CISA&#xff08;Cybersecurity and Infrastructure Security Agency&#xff09;等多个政府机构部门组成的ESF&#xff08;Enduring Security Framework&#xff0c;持久安全框架&#xff09…

scenic-view,一款冷门的JavaFX开发神器

用Java做图形界面本就很冷门&#xff0c;那么通过JavaFX开发GUI可能就更加冷门&#xff0c;毕竟前面还有Swing这位前辈。不过为了那些同为JavaFX的开发者&#xff0c;今天给大家带来一款JavaFX开发神器。 scenic-view Scenic View是一个JavaFX应用程序&#xff0c;旨在简化理…

【数据结构】深入探讨二叉树的遍历和分治思想(一)

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;数据结构 &#x1f525;该文章主要讲述二叉树的递归结构及分治算法的思想。 目录&#xff1a; &#x1f30d;前言&#xff1a;&#x1f30d;…

Redis和Mysql的数据一致性问题

在高并发的场景下&#xff0c;大量的请求直接访问Mysql很容易造成性能问题。所以我们都会用Redis来做数据的缓存&#xff0c;削减对数据库的请求的频率。 但是&#xff0c;Mysql和Redis是两种不同的数据库&#xff0c;如何保证不同数据库之间数据的一致性就非常关键了。 1、导…

​​SQLiteC/C++接口详细介绍之sqlite3类(十一)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;​​SQLiteC/C接口详细介绍之sqlite3类&#xff08;十&#xff09; 下一篇&#xff1a;​​SQLiteC/C接口详细介绍之sqlite3类&#xff08;十二&#xff09;&#xff08;未发表&#xff09; 33.sq…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Web)下篇

onRequestSelected onRequestSelected(callback: () > void) 当Web组件获得焦点时触发该回调。 示例&#xff1a; // xxx.ets import web_webview from ohos.web.webviewEntry Component struct WebComponent {controller: web_webview.WebviewController new web_webv…

鸿蒙开发实现弹幕功能

鸿蒙开发实现弹幕功能如下&#xff1a; 弹幕轮播组件&#xff1a;BannerScroll import type { IDanMuInfoList, IDanMuInfoItem } from ../model/DanMuData //定义组件 Component export default struct BannerScroll {//Watch 用来监视状态数据的变化&#xff0c;包括&#…