策略模式的理解和运用

在之前的小游戏项目中,处理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("长连接请求类型不存在")
		}
	}
}

更改后的代码整体结构:

总结

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

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

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

相关文章

【网络安全】【深度学习】【入侵检测】SDN模拟网络入侵攻击并检测,实时检测,深度学习

文章目录 1. 前言2. Mininet 和 Ryu 的区别2.1 Mininet2.2 Ryu2.3 总结 3. 模拟攻击3.1 环境准备3.2 创建 Mininet 网络拓扑3.2 启动 Ryu 控制器3.3 模拟网络攻击3.4 捕获流量 4. 实时异常检测4.1 在 Ryu 控制器中4.2 在 h2 机器上的实验结果4.3 深度学习模型部署上h2机器 帮助…

日进2000,我怎么做到的

昨天遇到一个有意思的项目,让我一天进账2000,一个字:爽。 这几天接洽了一位新客户,主要诉求就是优化系统,基于LNMP的系统优化。正好这个领域我比较熟悉,以前都是在公司做项目,也不怎么涉猎系统优…

展会邀请 | 龙智即将亮相2024上海国际嵌入式展,带来安全合规、单一可信数据源、可追溯、高效协同的嵌入式开发解决方案

2024年6月12日至14日,备受全球嵌入式系统产业和社群瞩目的2024上海国际嵌入式展(embedded world china 2024)即将盛大开幕,龙智将携行业领先的嵌入式开发解决方案亮相 640展位 。 此次参展,龙智将全面展示专为嵌入式行…

[C++]基于C++opencv结合vibe和sort tracker实现高空抛物实时检测

【vibe算法介绍】 ViBe算法是一种高效的像素级视频背景建模和前景检测算法。以下是对该算法的详细介绍: 一、算法原理 ViBe算法的核心思想是通过为每个像素点存储一个样本集,利用该样本集与当前像素值进行比较,从而判断该像素是否属于背景…

SpringBoot的学习要点

目录 SpringBoot 创建项目 配置文件 注解 命名规范 SpringBoot整合第三方技术 …… 中文文档:Spring Boot 中文文档 SpringBoot Spring Boot 是基于 Spring 框架的一种快速构建微服务应用的方式它主要提供了自动配置、简化配置、运行时应用监控等功能它…

vue2中的插槽使用以及Vuex的使用

插槽分为默认插槽&#xff0c;定名插槽还有作用域插槽 一.默认插槽&#xff0c;定名插槽 //app.vue <template> <div class"container"><CategoryTest title"美食" :listData"foods"><img slot"center" src&qu…

NotebookLM全新升级:Gemini 1.5 Pro助力全球研究与写作

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Python第二语言(九、Python第一阶段实操)

目录 1. json数据格式 2. Python与json之间的数据转换 3. pyecharts模块官网 4. pyecharts快速入门&#xff08;折线图&#xff09; 5. pyecharts全局配置选项 5.1 set_global_ops使用 5.1.1. title_opts 5.1.2 legend_opts 5.1.3 toolbox_opts 5.1.4 visualmap_opts…

L47---2706. 购买两块巧克力(排序)---Java版

1.题目描述 2.思路 &#xff08;1&#xff09;排序&#xff1a;从最便宜的巧克力开始组合 &#xff08;2&#xff09;双循环遍历所有可能的两块巧克力组合&#xff0c;计算其总花费。如果总花费小于等于 money&#xff0c;则更新最大剩余金额 &#xff08;3&#xff09;由于价…

度小满金融大模型的应用创新

XuanYuan/README.md at main Duxiaoman-DI/XuanYuan GitHub

jmeter性能优化之mysql监控sql慢查询语句分析

接上次博客&#xff1a;基础配置 多用户登录并退出jmx文件&#xff1a;百度网盘 提取码&#xff1a;0000 一、练习jmeter脚本检测mysql慢查询 随意找一个脚本(多用户登录并退出)&#xff0c;并发数设置300、500后分别查看mysql监控平台 启动后查看&#xff0c;主要查看mysql…

Linux安装MySQL教程【带图文命令巨详细】

巨详细Linux安装MySQL 1、查看是否有自带数据库或残留数据库信息1.1检查残留mysql1.2检查并删除残留mysql依赖1.3检查是否自带mariadb库 2、下载所需MySQL版本&#xff0c;上传至系统指定位置2.1创建目录2.2下载MySQL压缩包 3、安装MySQL3.1创建目录3.2解压mysql压缩包3.3安装解…

Ajax 快速入门

Ajax 概念&#xff1a;Ajax是一种Web开发技术&#xff0c;允许在不重新加载整个页面的情况下&#xff0c;与服务器交换数据并更新网页的部分内容。 作用&#xff1a; 数据交换&#xff1a;Ajax允许通过JavaScript向服务器发送请求&#xff0c;并能够接收服务器响应的数据。 异…

hustoj二开

目录 1、路径问题2、开发问题&#xff08;1&#xff09;、mysql&#xff08;2&#xff09;、php 啊啊啊啊&#xff01;&#xff01;&#xff01;难崩&#xff1a; 路径问题搞了好长时间才明白了该项目的路径如何设置的 >_< ,&#xff0c;本文就路径问题&#xff0c;前端页…

梯度提升决策树(GBDT)

GBDT&#xff08;Gradient Boosting Decision Tree&#xff09;&#xff0c;全名叫梯度提升决策树&#xff0c;是一种迭代的决策树算法&#xff0c;又叫 MART&#xff08;Multiple Additive Regression Tree&#xff09;&#xff0c;它通过构造一组弱的学习器&#xff08;树&am…

【C语言】轻松拿捏-联合体

谢谢观看&#xff01;希望以下内容帮助到了你&#xff0c;对你起到作用的话&#xff0c;可以一键三连加关注&#xff01;你们的支持是我更新地动力。 因作者水平有限&#xff0c;有错误还请指出&#xff0c;多多包涵&#xff0c;谢谢&#xff01; 联合体 一、联合体类型的声明二…

DDMA信号处理以及数据处理的流程---随机目标生成

Hello&#xff0c;大家好&#xff0c;我是Xiaojie&#xff0c;好久不见&#xff0c;欢迎大家能够和Xiaojie一起学习毫米波雷达知识&#xff0c;Xiaojie准备连载一个系列的文章—DDMA信号处理以及数据处理的流程&#xff0c;本系列文章将从目标生成、信号仿真、测距、测速、cfar…

RabbitMQ(五)集群配置、Management UI

文章目录 一、安装RabbitMQ1、前置要求2、安装docker版复制第一个节点的.erlang.cookie进入各节点命令行配置集群检查集群状态 3、三台组合集群安装版rabbitmq节点rabbitmq-node2节点rabbitmq-node3节点 二、负载均衡&#xff1a;Management UI1、说明2、安装HAProxy3、修改配置…