策略模式的理解和运用

news2024/11/24 12:23:55

在之前的小游戏项目中,处理websocket长连接请求的时候,需要根据传递数据包的不同类型,进行不同的处理。为了实现这个场景,比较简单的方法就是使用if-else或者switch-case语句,根据条件进行判断。但是这导致了项目代码复杂混乱,不利用项目后期的维护和扩展。

策略模式和观察者模式都可以解决这个大量条件判断的问题,相比之下,观察者模式比较复杂,适用于更加复杂的场景。而策略模式则比较轻便,简单易上手。

关于观察者模式,感兴趣的朋友可以看这一篇博客:

观察者模式的理解和引用-CSDN博客

旧代码如下

		//在信息中枢处根据消息类型进行特定的处理
		
		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()
			}
		}

策略模式介绍

策略模式是一种软件设计模式,它允许在运行时选择算法的行为。策略模式定义了一系列算法,并使它们能够相互替换,从而使算法可以独立于其使用者而变化。这意味着在实际使用中,可以在不修改其结构的情况下轻松地切换或替换算法。在我们的场景下,我们通过websocket连接获取到的数据包有很多的类型:CertificationType、CreateRoomType、JoinRoomType、RefreshScoreType、DiscontinueQuitType、GameOverType、HeartCheckType等类型,我们需要根据不同的类型对数据进行不同的处理。即在运行时,根据传入的Type值,选择不同的行为进行处理,使得我们可以在运行的过程中轻松切换行为。【这里可以将算法理解成对同样结构的输入,进行不同的处理。】

Context上下文对象类:该对象持有对策略接口的引用,可以根据需要动态地改变所使用的具体策略。这样,客户端调用上下文对象的方法时,就可以根据所设置的具体策略来执行相应的算法。

//将Strategy策略作为Context上下文对象的一个字段
//封装get、set、execStrategy(执行方法)
type Context struct {
	Strategy handler.Strategy 
}

func (c *Context) SetStrategy(strategy handler.Strategy) {
	c.Strategy = strategy
}

func (c *Context) GetStrategy() handler.Strategy {
	return c.Strategy
}

func (c *Context) ExecStrategy(client *pojo.Client, request req.WsReq) {
	c.Strategy.StrategyMethod(client, request)
}

Strategy策略接口:定义接口,接口里面是StrategyMethod()方法,在Context对象的ExecStrategy()方法中被调用。

type Strategy interface {
	StrategyMethod(client *pojo.Client, request req.WsReq)
}

ConcreteStrategy具体策略类:该类实现Strategy接口,并且根据自身的需要定义StrategyMethod()方法具体的业务代码。

type LifetimeCheck struct {
}

func (c *LifetimeCheck) StrategyMethod(client *pojo.Client, req req.WsReq) {
	if string(req.Data) == `"PING"` {
		client.User.Lifetime = client.User.Lifetime.Add(10 * time.Second)
	}

	wsResp := resp.WsResp{Type: req.Type, Data: "PONG"}
	if err := wsResp.WsResponseSuccess(client); err != nil {
		zap.L().Error("PING 返回出现错误")
		return
	}
}

客户端代码:创建上下文对象,根据需求,去调用SetStrategy()方法,并执行所设置策略的行为。我们的选择可以通过Map进行,根据map的key获取对应的具体策略对象。

func WsController(client *pojo.Client) {
	//map初始化之后就没有写入操作了,所以不存在线程安全的问题!!! 只会进行读取操作
	context := new(Context)
	
	//1.执行认证行为
	context.SetStrategy(new(specificHandler.CertificationStrategy))
	context.ExecStrategy(client, request)

	//2.执行心跳检测
	context.SetStrategy(new(specificHandler.LifetimeCheck))
	context.ExecStrategy(client, request)

	//1.执行聊天信息
	context.SetStrategy(new(specificHandler.ChatStrategy))
	context.ExecStrategy(client, request)
}

通过这样改造之后,我们的代码就可以实现:

  1. 算法可以独立于客户端而变化,客户端可以更灵活地选择算法。
  2. 可以避免大量的条件语句,将算法的选择和使用分离开来,提高了代码的可维护性和扩展性。
  3. 可以方便地增加、删除或更改算法,不会影响到客户端的代码。

总的来说,策略模式可以帮助我们更好地组织和管理不同的算法,使系统更加灵活、可扩展和易于维护。

策略选择改进

读到这里,大家可以发现,其实还是没有办法解决我们代码里面出现的大量if - else语句,我们需要通过if - else来做判断,帮助我们选择对应的行为。其实,这里可以引入map帮助我们来做选择,如果命中map中的key,就返回对应的算法对象。
代码如下:

func WsController(client *pojo.Client) {
	//map初始化之后就没有写入操作了,所以不存在线程安全的问题!!! 只会进行读取操作
	context := new(Context)
	handlerMap := map[string]handler.Strategy{}

	//对客户端发送过来的对应请求类型,进行注册操作
	handlerMap[CertificationType] = new(specificHandler.CertificationStrategy)
	handlerMap[ChatType] = new(specificHandler.ChatStrategy)
	handlerMap[CommentNotificationType] = new(specificHandler.CommentNotification)
	handlerMap[FansNotificationType] = new(specificHandler.FansNotification)
	handlerMap[CloseType] = new(specificHandler.CloseConnection)
	handlerMap[LifetimeCheckType] = new(specificHandler.LifetimeCheck)

	for {
		request := req.WsReq{}
		//循环读取对应的类型,并进行反序列化
		client.Conn.ReadJSON(&request)
		//通过定义的map,通过对应的数据类型获取算法对象。如果能够获取到更改上下文,并执行匹配到的策略,处理数据
		if strategy, ok := handlerMap[request.Type]; ok {
			context.SetStrategy(strategy)
			context.ExecStrategy(client, request)
		} else {
			zap.L().Error("长连接请求类型不存在")
		}
	}
}

更改后的代码整体结构:

总结

代码并不是能跑就可以了,如果可以的话,请尽可能地优化自己的代码,让自己的代码利于维护和扩展,可读性高。

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

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

相关文章

梦幻西游外网架设教程-端游篇

《梦幻西游》是一款由中国网易公司自行开发并营运的网络国产游戏。游戏以著名的章回小说《西游记》故事为背景,透过Q版的人物,试图营造出浪漫的网络游戏风格。 《梦幻西游》拥有注册用户超过3.1亿,一共开设收费服务器达472组,最高…

Sigmoid图像

import matplotlib.pyplot as plt import numpy as npdef sigmoid(x):# 直接返回sigmoid函数return 1. / (1. np.exp((0.7 -(x))/0.075))def plot_sigmoid():# param:起点,终点,间距x np.arange(0, 1, 1 / 16000)y sigmoid(x)plt.plot(x, y)plt.show…

【WP】猿人学_19_乌拉乌拉乌拉

https://match.yuanrenxue.cn/match/19 发包测试 经过发包测试,并没有携带加密参数,但是使用python无法复现,requests,httpx以及异步都不行,网上搜索了一下,这是使用了JA3指纹。可能是我做的时间比较晚&…

【iOS】UI学习——导航控制器、分栏控制器

UI学习(三) 导航控制器导航控制器基础导航控制器切换导航栏和工具栏 分栏控制器分栏控制器基础分栏控制器高级 导航控制器 导航控制器负责控制导航栏(navigationBar),导航栏上的按钮叫UINavigationItem(导航…

【论文阅读】SELF-RAG,让模型决策和反思检索

关于LLM何时使用RAG的问题,原本是阅读了关于ADAPT-LLM模型的那篇论文,被问到与SELF-RAG有何区别。所以,大概看了一下SELF-RAG这篇论文,确实很像,这些基于LLM针对下游任务的模型架构和方法,本来就很像。不过…

accelerate笔记:实验跟踪

Accelerate支持七种集成的跟踪器: TensorBoardWandBCometMLAimMLFlowClearMLDVCLive要使用这些跟踪器,可以通过在 Accelerator 类的 log_with 参数中传入所选类型来实现 from accelerate import Accelerator from accelerate.utils import LoggerTypeac…

Yolo-v5模型训练速度,与GeForce的AI算力描述

1.GeForce RTX3070 Ti官网参数: GeForce RTXTM 3070 Ti 和 RTX 3070 显卡采用第 2 代 NVIDIA RTX 架构 - NVIDIA Ampere 架构。该系列产品搭载专用的第 2 代 RT Core ,第 3 代 Tensor Core、全新的 SM 多单元流处理器以及高速显存,助您在高性…

北斗RTK+UWB定位的优势

在当今科技飞速发展的时代,定位技术的应用已渗透到我们生活的方方面面。从导航、物流到无人驾驶、智能制造,精准定位技术无处不在。而北斗RTK(Real-Time Kinematic,实时动态)和UWB(Ultra-Wideband&#xff…

闭眼推荐的,新手教师工具

亲爱的老师们,尤其是那些刚踏入教育界的新手教师们,还在为如何高效管理课堂、如何制作精美的教学材料而头疼吗?让我来分享几款教育界口碑爆棚的工具。 易查分小程序 易查分是一款超级方便的成绩查询工具,一分钟就能上传成绩并生成…

[经验] 腰果树的外观特征和特点是什么 #媒体#微信

腰果树的外观特征和特点是什么 腰果树是一种生长在热带和亚热带地区的落叶乔木,其叶子为互生,倒披针形或披针形,整个树枝条生长勃勃,长势喜人。 腰果树的树皮是灰色或深褐色的,有着纵向裂缝,树皮粗糙而有光…

【Mybatis】源码分析-高级应用

1、Mybatis配置文件深入理解 1.2、动态SQL语句 Mybatis 的映射⽂件中,前⾯我们的 SQL 都是⽐较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,此时在前⾯的学习中我们的 SQL 就不能满⾜要求了。 1.2.1、条件判断 我们根…

OrangePi KunPengPro | linux系统下挂载U盘

OrangePi KunPengPro | linux系统下挂载U盘 时间:2024年6月6日21:32:53 文章目录 OrangePi KunPengPro | linux系统下挂载U盘1.参考2.操作fdisk -l 列出系统上所有磁盘的分区表信息将 /dev/sda1 分区挂载到 /mnt/udisk/ 目录显示文件系统的磁盘空间使用情况卸载文件…

【C++】问题及补充(2)

string s2“hello word”;是怎么进行隐式类型转换的 在这里,"hello world"是一个C字符串常量,而s2是一个std::string类型的变量。当你将C字符串常量赋值给一个std::string类型的变量时,会发生隐式类型转换。编译器会将C字符串常量转…

机器学习笔记 - 本地windows 11 + PyCharm运行stable diffusion流程简述

一、环境说明 硬件:本地电脑windows11、32.0 GB内存、2060的6G的卡。 软件:本地有一个python环境,主要是torch 2.2.2+cu118 二、准备工作 1、下载模型 https://huggingface.co/CompVishttps://huggingface.co/CompVis 进入上面的网址,我这里下载的是这个里面的 …

Go微服务: 分布式Cap定理和Base理论

分布式中的Cap定理 CAP理论 C: 一致性,是站在分布式的角度,要么读取到数据,要么读取失败,比如数据库主从,同步时的时候加锁,同步完成才能读到同步的数据,同步完成,才返回数据给程序&…

【ssh命令】ssh登录远程服务器

命令格式:ssh 用户名主机IP # 使用非默认端口: -p 端口号 ssh changxianrui192.168.100.100 -p 1022 # 使用默认端口 22 ssh changxianrui192.168.100.100 然后输入密码,就可以登录进去了。

多模态vlm综述:An Introduction to Vision-Language Modeling 论文解读

目录 1、基于对比学习的VLMs 1.1 CLIP 2、基于mask的VLMs 2.1 FLAVA 2.2 MaskVLM 2.3 关于VLM目标的信息理论视角 3、基于生成的VLM 3.1 学习文本生成器的例子: 3.2 多模态生成模型的示例: 3.3 使用生成的文本到图像模型进行下游视觉语言任务 4、 基于预训练主干网…

Vue——子级向父级使用props传递数据(函数)

文章目录 前言原理案例效果演示 前言 看到这个标题,相信很多人会说我,你之前博客写的父级向子级中传递数据使用的是props,然后说的子级向父级传递数据则是用的$emit。 并且还说了对于String、数组Array,只能是父级使用props传递…

UE5 Mod Support 思路——纯蓝图

原创作者:Chatouille 核心功能 “Get Blueprint Assets”节点,用于加载未来的mod。用基础类BP_Base扩展即可。打包成补丁,放到Content\Paks目录下,即可让游戏访问到内容。 与文中所写不同的地方 5.1或者5.2开始,打…

音视频开发—V4L2介绍,FFmpeg 打开摄像头输出yuv文件

实验平台:Ubuntu20.04 摄像头:1080P 监控摄像头,采用V4L2驱动框架 文章目录 1.V4L2相关介绍1.1. 基本概念1.2. 主要功能1.3. V4L2驱动框架1.4. 主要组件1.5. 使用V4L2的应用1.6. 常用V4L2工具 2.ffmpeg命令实现打开摄像头输出yuv文件3.使用C…