如何利用命令模式实现一个手游后端架构?

devtools/2024/9/23 6:34:01/

命令模式的原理解读

命令模式的英文翻译是 Command Design Pattern。在 GoF 的《设计模式》一书中,它是这么定义的:

The command pattern encapsulates a request as an object, thereby letting us parameterize other objects with different requests, queue or log requests, and support undoable operations.

翻译成中文就是下面这样:

命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等(附加控制)功能。

落实到编码实现,命令模式用的最核心的实现手段,是将函数封装成对象。C 语言支持函数指针,我们可以把函数当作变量传递来传递去。但是,在大部分编程语言中,函数没法儿作为参数传递给其他函数,也没法儿赋值给变量。借助命令模式,我们可以将函数封装成对象。具体来说就是,设计一个包含这个函数的类,实例化一个对象传来传去,这样就可以实现把函数像对象一样使用。从实现的角度来说,它类似回调。

当我们把函数封装成对象之后,对象就可以存储下来,方便控制执行。所以,命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。

命令模式的实战解析

假设我们正在开发一个类似《天天酷跑》或者《QQ 卡丁车》这样的手游。这种游戏本身的复杂度集中在客户端。后端基本上只负责数据(比如积分、生命值、装备)的更新和查询,所以,后端逻辑相对于客户端来说,要简单很多。

为了提高性能,我们会把游戏中玩家的信息保存在内存中。在游戏进行的过程中,只更新内存中的数据,游戏结束之后,再将内存中的数据存档,也就是持久化到数据库中。为了降低实现的难度,一般来说,同一个游戏场景里的玩家,会被分配到同一台服务上。这样,一个玩家拉取同一个游戏场景中的其他玩家的信息,就不需要跨服务器去查找了,实现起来就简单了很多。

一般来说,游戏客户端和服务器之间的数据交互是比较频繁的,所以,为了节省网络连接建立的开销,客户端和服务器之间一般采用长连接的方式来通信。通信的格式有多种,比如 Protocol Buffer、JSON、XML,甚至可以自定义格式。不管是什么格式,客户端发送给服务器的请求,一般都包括两部分内容:指令和数据。其中,指令我们也可以叫作事件,数据是执行这个指令所需的数据。

服务器在接收到客户端的请求之后,会解析出指令和数据,并且根据指令的不同,执行不同的处理逻辑。对于这样的一个业务场景,一般有两种架构实现思路。

常用的一种实现思路是利用多线程。一个线程接收请求,接收到请求之后,启动一个新的线程来处理请求。具体点讲,一般是通过一个主线程来接收客户端发来的请求。每当接收到一个请求之后,就从一个专门用来处理请求的线程池中,捞出一个空闲线程来处理。

另一种实现思路是在一个线程内轮询接收请求和处理请求。这种处理方式不太常见。尽管它无法利用多线程多核处理的优势,但是对于 IO 密集型的业务来说,它避免了多线程不停切换对性能的损耗,并且克服了多线程编程 Bug 比较难调试的缺点,也算是手游后端服务器开发中比较常见的架构模式了。

我们接下来就重点讲一下第二种实现方式。

整个手游后端服务器轮询获取客户端发来的请求,获取到请求之后,借助命令模式,把请求包含的数据和处理逻辑封装为命令对象,并存储在内存队列中。然后,再从队列中取出一定数量的命令来执行。执行完成之后,再重新开始新的一轮轮询。具体的示例代码如下所示:

public interface Command {void execute();
}public class GotDiamondCommand implements Command {// 省略成员变量public GotDiamondCommand(/*数据*/) {//...}@Overridepublic void execute() {// 执行相应的逻辑}
}
//GotStartCommand/HitObstacleCommand/ArchiveCommand类省略public class GameApplication {private static final int MAX_HANDLED_REQ_COUNT_PER_LOOP = 100;private Queue<Command> queue = new LinkedList<>();public void mainloop() {while (true) {List<Request> requests = new ArrayList<>();//省略从epoll或者select中获取数据,并封装成Request的逻辑,//注意设置超时时间,如果很长时间没有接收到请求,就继续下面的逻辑处理。for (Request request : requests) {Event event = request.getEvent();Command command = null;if (event.equals(Event.GOT_DIAMOND)) {command = new GotDiamondCommand(/*数据*/);} else if (event.equals(Event.GOT_STAR)) {command = new GotStartCommand(/*数据*/);} else if (event.equals(Event.HIT_OBSTACLE)) {command = new HitObstacleCommand(/*数据*/);} else if (event.equals(Event.ARCHIVE)) {command = new ArchiveCommand(/*数据*/);} // ...一堆else if...queue.add(command);}int handledCount = 0;while (handledCount < MAX_HANDLED_REQ_COUNT_PER_LOOP) {if (queue.isEmpty()) {break;}Command command = queue.poll();command.execute();}}}
}

命令模式 VS 策略模式

看了刚才的讲解,你可能会觉得,命令模式跟策略模式、工厂模式非常相似,那它们的区别在哪里呢?

实际上,每个设计模式都应该由两部分组成:第一部分是应用场景,即这个模式可以解决哪类问题;第二部分是解决方案,即这个模式的设计思路和具体的代码实现。不过,代码实现并不是模式必须包含的。如果你单纯地只关注解决方案这一部分,甚至只关注代码实现,就会产生大部分模式看起来都很相似的错觉。

设计模式之间的主要区别还是在于设计意图,也就是应用场景。单纯地看设计思路或者代码实现,有些模式确实很相似,比如策略模式和工厂模式。

策略模式包含策略的定义、创建和使用三部分,从代码结构上来,它非常像工厂模式。它们的区别在于,策略模式侧重“策略”或“算法”这个特定的应用场景,用来解决根据运行时状态从一组策略中选择不同策略的问题,而工厂模式侧重封装对象的创建过程,这里的对象没有任何业务场景的限定,可以是策略,但也可以是其他东西。从设计意图上来,这两个模式完全是两回事儿。

有了刚刚的铺垫,接下来,我们再来看命令模式跟策略模式的区别。你可能会觉得,命令的执行逻辑也可以看作策略,那它是不是就是策略模式了呢?实际上,这两者有一点细微的区别。

在策略模式中,不同的策略具有相同的目的、不同的实现、互相之间可以替换。比如,BubbleSort、SelectionSort 都是为了实现排序的,只不过一个是用冒泡排序算法来实现的,另一个是用选择排序算法来实现的。而在命令模式中,不同的命令具有不同的目的,对应不同的处理逻辑,并且互相之间不可替换。

落实到编码实现,命令模式用到最核心的实现手段,就是将函数封装成对象。在大部分编程语言中,函数是没法作为参数传递给其他函数的,也没法赋值给变量。借助命令模式,将函数封装成对象,这样就可以实现把函数像对象一样使用。

命令模式的主要作用和应用场景,是用来控制命令的执行,比如,异步、延迟、排队执行命令、撤销重做命令、存储命令、给命令记录日志等等,这才是命令模式能发挥独一无二作用的地方。


http://www.ppmy.cn/devtools/109929.html

相关文章

NVIDIA Triton Inference Server 部署 yolov5

文章目录 一、拉取 tensorrt 、yolov5、tritonserver 镜像二、下载 yolov5-6.2、tensorrtx/yolov5-6.2源码三、pt转wts四、wts转engine五、创建triton推理服务器六、创建客户端进行测试 一、拉取 tensorrt 、yolov5、tritonserver 镜像 docker pull hakuyyf/tensorrtx:trt8.2_…

Linux /tmp/下的文件自动清理

在使用systemd程序的Linux系统中&#xff0c;/tmp 目录下的文件会自动清理。默认情况下&#xff0c;系统使用 systemd-tmpfiles-clean 服务来管理 /tmp 目录中的文件&#xff0c;并根据文件的访问时间来决定何时删除它们。具体清理策略取决于配置文件 /usr/lib/tmpfiles.d/tmp.…

【Go】Go语言介绍与开发环境搭建

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

批量创建文件夹和文件——excel VBA实现

当需要创建大量文件夹及文件时&#xff0c;可借助excel vba 实现&#xff0c;如下图&#xff1a; 批量创建文件名为1-10的文件夹&#xff0c;每个文件夹内有个与文件名相同的txt文件&#xff0c;txt文件内的数字也跟文件名相同。 附代码&#xff1a; Sub CreateFoldersAndFile…

【区块链通用服务平台及组件】信息数据流转验真技术研究项目 | FISCO BCOS应用案例

在日常工作中&#xff0c;相关系统每天会产生大量数据&#xff0c;系统之间有多种模式数据交互方式&#xff0c;数据监管工作量巨大&#xff0c;急需 数据追溯定位工具来辅助监管&#xff1b;数据在生产过程中经常会出现采集、提交、修改、删除等操作&#xff0c;需要对数据变更…

3个恢复方法详解:iPhone手机快速找回备忘录

当我们在工作或者是学习时&#xff0c;总会有一些灵光乍现的好想法&#xff0c;我们通常会将这些想法记录在iPhone手机备忘录中&#xff0c;以便随时查看。但是&#xff0c;如果出现不慎删除备忘录的情况&#xff0c;iPhone该如何找回备忘录呢&#xff1f;不用担心&#xff0c;…

windows修改升级时间

按键盘上的【 Win R 】组合键&#xff0c;打开运行&#xff0c;然后输入【regedit】命令&#xff0c;再按【确定或回车】&#xff0c;打开注册表编辑器&#xff1b; 3、注册表编辑器窗口&#xff0c;依次展开到以下路径&#xff1a; HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Wi…

基于微信的热门景点推荐小程序的设计与实现(论文+源码)_kaic

摘 要 近些年来互联网迅速发展人们生活水平也稳步提升&#xff0c;人们也越来越热衷于旅游来提高生活品质。互联网的应用与发展也使得人们获取旅游信息的方法也更加丰富&#xff0c;以前的景点推荐系统现在已经不足以满足用户的要求了&#xff0c;也不能满足不同用户自身的个…