前言
在近期的公司项目中,需求项目开发一个类沙盒建造功能。我有幸负责项目中的关键部分,这一经历充满了挑战与收获。鉴于这个部分内容的丰富性和技术性,我决定将涉及相关设计以及本人的一部分思绪分成四期内容。本文是系列文章的第二期,将重点讨论建造中的命令模式设计。
为什么会有命令模式相关的内容呢? 允许用户根据不同的请求对操作进行参数化、排队、记录日志以及撤销操作等。这是大部分带有编辑模式内容不可会缺的一环, 本篇将从基础的命令模式内容讲起, 到业务层基础的命令搭建, 再到整体的框架设计,这篇文章是系列讲解中的重点之一.
命令模式
命令模式(Command Pattern)是一种行为设计模式,它将请求或简单操作封装为一个对象,这样可以使用不同的请求、队列或日志来参数化其他对象。同时,它也支持可撤销的操作。
关于行为设计模式
命令模式通常包括四个部分: 命令接口、具体命令(ConcreteCommand)、接收者(Receiver)、调用者(Invoker)
命令接口
这部分内容比较简单,基本就是命令的基本声明,如Execute、Undo、Type、Redo等等
不同语言的实现方式也有不用, 可以用基类, 也可以使用接口, 甚至是协议
实现几个空方法即可
这里笔者由于使用的语言语法糖相对落后 游戏开发者苦Lua久已
所以使用的是基类的方法实现 还比上述多一个Init, 然后也没有Redo的需求
具体命令
再详细编写写命令之前, 首先需要抽象需求内容, 比如笔者这边是几个需求:
Create、Delete、Move、Rotate、Style、Replace
需要针对他们创建不同的具体命令子类
关于命令类型
根据抽象出来的命令种类, 执行对应的方法
在这一步笔者的做法是 将每一个方法的执行地图元素Element(传统上命令模式的接收者)放到了基类的 Init中
在Execute、Undo中执行Element的对应方法
从而达成每一个命令的执行逻辑
接收者
上文其实也有所提及, 命名听起来与理解起来还是有点绕的, 但它其实就是命令内容的执行者.
笔者这里是将基类命令的初始化函数传入了接收者, 不在初始化部分传入也是可以的, 倘若你只有一个单一接收者, 那么在所有命令的操作中再次传入即可.
调用者
调用者是命令模式的灵魂之一, 没有调用者维护命令的数据结构那么命令就是一盘散沙, 在通常理解中调用者大概有两个形式, 栈与队列, 各有优劣
- 栈: 由于栈后进先出的特性, 能够非常完美的实现撤销、重做操作, 与用户期望操作顺序相同, 是绝大部分命令模式的首先调用者格式, 同时也是作者本次选用的格式
- 队列: 由于队列先进先出的特性, 能够实现异步处理、批处理, 这些需要指定运行顺序的内容, 虽然在需求上没有那么常见, 但是对性能优化上起着这关无上的作用
笔者这次需要实现的需求是两个内容: 当前命令操作栈 和 所有命令维护数组
操作栈
主要负责用户输入命令的添加, 撤回, 但其实上内容就是一个普通的范型栈, 低能lua依然不支持, 当然他会比普通的栈多两个方法, 撤销当前命令 和 撤销当前栈命令, 两者有个遍历关系, 当用户确认操作无误后将当前命令操作栈记录到栈数组中
命令维护数组
该数组负责所有命令相关的撤销与确认, 同时客户端上需要负责diff, 并将结果同步.
这里主要聊聊diff操作:
主要分为三个部分, 创建、更新、删除
创建主要跟Create命令强相关
删除主要跟Delete命令强相关, 此时需要注意, 如果是新创建的需要从创建部分删去, 否则从更新部分删除并添加至删除部分
更新就是其他普遍命令了
其他形式
更新方式其实有很多样的结果, 比如每次行程快照进行比对, 甚至可以把相关命令,传上去都是可以操作的选择
尾语
这部分就是命令模式的大体内容了, 因为这个模式的思路会比代码更重要, 所以其实笔者并没有针对某个语言去详细展开, 所以也就是没有直接上相关代码, 万变不离其宗, 望大家有所收获, 下一篇是有关状态模式的内容敬请期待.