持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第10天,点击查看活动详情
前言
最近项目开发需要用到命令模式,之前没学过,就过去研究了下。但是,理解后发现网上对于命令模式的讲解大多晦涩难懂,并且在选例时容易让人进入一个误区。
所以,干脆自己写篇命令模式的讲解,用于自我总结,当然,若能帮上各位同学,那就是赚了。
命令模式
首先我们需要清楚命令模式的使用场景,更多是用于存储行为操作。就像你想记录用户各种行为,将其写入日志中。但是,跟写日志又有点稍微不同的是,写日志是一次性行为,一旦写入文件中,就抛弃该行为了,而命令模式是把这些行为记录在内存中,方便后续对于这些行为进行管理。
例如在画图的时候,你画了一条直线,然后你觉得不够直,就将其取消了,但是后面想想,这直线感觉还行,又将其还原了。画直线是一种行为,而一旦你存储了这种行为,就很方便你进行撤销或者重制的功能了。
说了这么多,感觉可能还有点难理解,我们就以写字板作为一个🌰进行解读:
写个写字板的类:WordPad.kt
,里面含有绘画矩形和绘画圆形的功能:
class WordPad {
fun drawRect(){
println("绘画了一个矩形")
}
fun drawCircle(){
println("绘画了一个圆形")
}
}
复制代码
这时,如果我们想绘画个矩形和圆形,该怎么做?
WordPad().drawRect()
? WordPad().drawCircle()
?
当然不能这样做,这样做的话,功能是可以实现,但是我们很难去存储这种行为,所以,我们需要定义一个接口,专门去执行某种行为,然后就可以通过 List<接口>
去存储这些行为:
interface ICommand {
fun execute()
}
复制代码
绘画矩形行为:
class RectCommand(val wordPad: WordPad) : ICommand {
override fun execute() {
wordPad.drawRect()
}
}
复制代码
绘画圆形行为:
class RectCommand(val wordPad: WordPad) : ICommand {
override fun execute() {
wordPad.drawRect()
}
}
复制代码
所以,想绘画矩形或者圆形该这样做:
val wordPad = WordPad()
RectCommand(wordPad).execute() // 绘画矩形
CircleCommand(wordPad).execute() // 绘画圆形
复制代码
日志输出:
I/System.out: 绘画了一个矩形
I/System.out: 绘画了一个圆形
复制代码
好了,现在我们需要去存储行为了:
object Invoker {
val commandList = mutableListOf<ICommand>()
fun executeCommand(command: ICommand){
command.execute()
commandList.add(command)
}
}
复制代码
这样,后续的行为都由 Invoker 代替执行:
val wordPad = WordPad()
Invoker.executeCommand(RectCommand(wordPad)) // 绘画矩形
Invoker.executeCommand(CircleCommand(wordPad)) // 绘画圆形
复制代码
同时,由于 Invoker 已经存储了这些行为,我那么就可以对这些行为进行管控了,例如重复用户第一次行为,或者重复用户最后一次行为,或者重复用户所有行为:
object Invoker {
val commandList = mutableListOf<ICommand>()
fun executeCommand(command: ICommand) {
command.execute()
commandList.add(command)
}
// 重复所有行为
fun repeat() = commandList.forEach {
it.execute()
}
// 执行第一次
fun doFirst() = commandList.first().execute()
// 执行最后一次
fun doLast() = commandList.last().execute()
}
复制代码
val wordPad = WordPad()
Invoker.executeCommand(RectCommand(wordPad)) // 绘画矩形
Invoker.executeCommand(CircleCommand(wordPad)) // 绘画圆形
println("重复用户全部行为:")
Invoker.repeat()
println("重复用户第一次行为:")
Invoker.doFirst()
println("重复用户最后一次行为:")
Invoker.doLast()
复制代码
I/System.out: 绘画了一个矩形
I/System.out: 绘画了一个圆形
I/System.out: 重复用户全部行为:
I/System.out: 绘画了一个矩形
I/System.out: 绘画了一个圆形
I/System.out: 重复用户第一次行为:
I/System.out: 绘画了一个矩形
I/System.out: 重复用户最后一次行为:
I/System.out: 绘画了一个圆形
复制代码
误区
初步看的话,会发现上面讲的命令模式是没什么问题的,确实也是可以这样实现的。但是,它却是有一个很容易让人理解错误的误区,那就是 WordPad。
因为对于命令模式来说,最核心的问题在于对命令的封装以及管控,所以,即使去掉了 WordPad 也不会有任何影响!
下面我们把 RectCommand 和 CircleCommand 也相应改下:
class RectCommand : ICommand {
override fun execute() {
println("绘画了一个矩形")
}
}
复制代码
class CircleCommand : ICommand {
override fun execute() {
println("绘画了一个圆形")
}
}
复制代码
执行代码也改下:
Invoker.executeCommand(RectCommand()) // 绘画矩形
Invoker.executeCommand(CircleCommand()) // 绘画圆形
println("重复用户全部行为:")
Invoker.repeat()
println("重复用户第一次行为:")
Invoker.doFirst()
println("重复用户最后一次行为:")
Invoker.doLast()
复制代码
会发现跟输出结果并没有什么不同。因为对于 Invoker 而言,它无需知道每个命令的具体执行细节,它只负责调用和记录即可。但是,由于大部分的命令模式的讲解文都会从类似 WordPad 的类进行说起,就会让人感觉,好像命令模式是跟某个工具类进行绑定一样,还可能会感觉,该工具类只能存在一个。
思考与探索。