命令模式很多同学可能不会用到,但这个模式还是蛮有意思的。命令模式能够将操作和数据打包成对象,便于系统对命令进行管理、维护。
UML类图位置:https://www.processon.com/view/link/60d29bf3e401fd49502afd25
本文代码链接为:https://github.com/shidawuhen/asap/blob/master/controller/design/26command.go
1.定义
1.1命令模式
命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
UML:
1.2分析
上面的定义和UML比较复杂,大家可能比较难理解。
首先我们需要明白什么是命令。命令包括指令和数据。指令是行为,数据影响到指令。如前进3米,前进是指令,3米是数据。
然后我们再看一下各个类的含义。
Command和ConcreteCommand是命令,有Excute函数,代表要做的行为。
ConcreteCommand调用Excute(),最终调用Receiver的Action。这意味ConcreteCommand只是一个容器,真正的操作逻辑在Receiver中。
Invoker包含了所有Command,控制Command何时执行Excute()。
现在我们将UML简化,把Invoker、Receiver去掉,看看是否容易理解了。
通过这个简洁版UML,我们来看一下为什么要用命令模式。
命令包括指令和数据,指令其实对应着操作,操作在代码中对应着函数。
命令模式其实是把函数封装成对象,系统能对对象进行各种操作,如排队执行、记录日志、撤销等。
为什么要将函数包装成对象呢?C、C++、Go支持函数指针,但并不是所有语言都有这种特性,这时命令模式就起作用了。而且即使语言支持函数指针,命令的数据部分怎么存放仍是一个问题。
所以简单理解,命令模式就是把请求打包成一个一个Command对象,存储起来,系统根据实际需求进行处理。
2.应用场景
大家可能感觉命令模式与MQ、工厂模式一样,其实在细节上是有区别的:
- MQ只包含数据,不包含行为,命令模式两者都包含
- 工厂模式需要实时执行,但命令模式可以进行存储,延后执行
命令模式我从来没有用过。《设计模式之美》里讲了游戏研发的一种通用架构:客户端的请求被服务端存储起来,服务端有单独线程处理这些请求。那我们就按这个场景写一下代码实现。
3.代码实现
1 | package main |
输出:
➜ myproject go run main.go
使用技能野蛮冲撞
向右移动10,向上移动20
向右移动10,向上移动20
使用技能野蛮冲撞
通过上面的代码,大家应该能够理解命令模式了。可以看出,对不同请求,生成不同的Command,Command中包含对应的数据与操作。这也是模式定义中说到的”对请求排队或记录请求日志,以及支持可撤销的操作“。
总结
设计模式是为了解决现实中的问题,我们需要和具体场景相绑定。在解决问题的时候,采用的是不是标准的设计模式并不重要,模式只是手段,手段需要为达成目的服务。