原理
将命令封装成对象,从而对命令进行一些附加控制(延迟、异步、排队执行,撤销重做,存储,记录日志等等)
目的
控制命令的执行
最佳实践
需求背景
假设我们正在开发一个类似《天天酷跑》或者《QQ 卡丁车》这样的手游。这种游戏本身的复杂度集中在客户端。后端基本上只负责数据(比如积分、生命值、装备)的更新和查询,所以,后端逻辑相对于客户端来说,要简单很多。
一般来说,游戏客户端和服务器之间的数据交互是比较频繁的,所以,为了节省网络连接建立的开销,客户端和服务器之间一般采用长连接的方式来通信。通信的格式有多种,比如 Protocol Buffer、JSON、XML,甚至可以自定义格式。不管是什么格式,客户端发送给服务器的请求,一般都包括两部分内容:指令和数据。
服务器在接收到客户端的请求之后,会解析出指令和数据,并且根据指令的不同,执行不同的处理逻辑。对于这样的一个业务场景,一般有两种架构实现思路。
常用的一种实现思路是利用多线程。一个线程接收请求,接收到请求之后,启动一个新的线程来处理请求。具体点讲,一般是通过一个主线程来接收客户端发来的请求。每当接收到一个请求之后,就从一个专门用来处理请求的线程池中,捞出一个空闲线程来处理。
另一种实现思路是在一个线程内轮询接收请求和处理请求。这种处理方式不太常见。尽管它无法利用多线程多核处理的优势,但是对于 IO 密集型的业务来说,它避免了多线程不停切换对性能的损耗,并且克服了多线程编程 Bug 比较难调试的缺点,也算是手游后端服务器开发中比较常见的架构模式了。
我们接下来就重点讲一下第二种实现方式。
整个手游后端服务器轮询获取客户端发来的请求,获取到请求之后,借助命令模式,把请求包含的数据和处理逻辑封装为命令对象,
并存储在内存队列中。然后,再从队列中取出一定数量的命令来执行。执行完成之后,再重新开始新的一轮轮询。
代码实现
type RequestInfo struct {
command string
data interface{}
}
type Command interface {
Execute()
}
type GotStartCommand struct {
data interface{}
}
func NewGotStartCommand(data interface{}) *GotStartCommand {
return &GotStartCommand{
data: data,
}
}
func (c *GotStartCommand) Execute() {
println("获取星星处理逻辑")
}
type GotDiamondCommand struct {
data interface{}
}
func NewGotDiamondCommand(data interface{}) *GotDiamondCommand {
return &GotDiamondCommand{
data: data,
}
}
func (c *GotDiamondCommand) Execute() {
println("获取钻石处理逻辑")
}
func ApplicationMain() {
maxHandleReqCountByOnce := 100
commandQueue := make([]Command, 0)
for {
requestInfoList := make([]*RequestInfo, 0)
//调用epoll、select获取传过来的数据,封装成request
for i := range requestInfoList {
req := requestInfoList[i]
if req.command == "got_star" {
commandQueue = append(commandQueue, NewGotStartCommand(req.data))
} else if req.command == "got_diamond" {
commandQueue = append(commandQueue, NewGotDiamondCommand(req.data))
}
}
handleCommandCount := 0
for i := 0; i < len(commandQueue); i++ {
if handleCommandCount > maxHandleReqCountByOnce {
break
}
commandQueue[i].Execute()
handleCommandCount += 1
}
if handleCommandCount == len(commandQueue) {
commandQueue = make([]Command, 0)
} else {
commandQueue = commandQueue[handleCommandCount:]
}
}
}
总结
命令模式VS策略模式:
命令模式是根据不同的命令执行不同的逻辑,策略模式是根据不同的情况选择不同的策略,看上去是相似的,但本质其实不同
命令模式中不同的命令类,它们的目的、逻辑都不相同,不可以相互替换
策略模式中不同的策略类,它们的逻辑不相同,但是它们的目的相同可以相互替换