• 关于命令模式的误区,你知道了吗


    前言

    最近项目开发需要用到命令模式,之前没学过,就过去研究了下。但是,理解后发现网上对于命令模式的讲解大多晦涩难懂,并且在选例时容易让人进入一个误区。

    所以,干脆自己写篇命令模式的讲解,用于自我总结,当然,若能帮上各位同学,那就是赚了。

    命令模式

    首先我们需要清楚命令模式的使用场景,更多是用于存储行为操作。就像你想记录用户各种行为,将其写入日志中。但是,跟写日志又有点稍微不同的是,写日志是一次性行为,一旦写入文件中,就抛弃该行为了,而命令模式是把这些行为记录在内存中,方便后续对于这些行为进行管理。

    例如在画图的时候,你画了一条直线,然后你觉得不够直,就将其取消了,但是后面想想,这直线感觉还行,又将其还原了。画直线是一种行为,而一旦你存储了这种行为,就很方便你进行撤销或者重制的功能了。

    说了这么多,感觉可能还有点难理解,我们就以写字板作为一个🌰进行解读:

    写个写字板的类:WordPad.kt ,里面含有绘画矩形和绘画圆形的功能:

    class WordPad {
        fun drawRect(){
            println("绘画了一个矩形")
        }
        fun drawCircle(){
            println("绘画了一个圆形")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这时,如果我们想绘画个矩形和圆形,该怎么做?

    WordPad().drawRect() ? WordPad().drawCircle() ?

    当然不能这样做,这样做的话,功能是可以实现,但是我们很难去存储这种行为,所以,我们需要定义一个接口,专门去执行某种行为,然后就可以通过 List<接口> 去存储这些行为:

    interface ICommand {
        fun execute()
    }
    
    • 1
    • 2
    • 3

    绘画矩形行为:

    class RectCommand(val wordPad: WordPad) : ICommand {
        override fun execute() {
            wordPad.drawRect()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    绘画圆形行为:

    class RectCommand(val wordPad: WordPad) : ICommand {
        override fun execute() {
            wordPad.drawRect()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    所以,想绘画矩形或者圆形该这样做:

            val wordPad = WordPad()
            RectCommand(wordPad).execute() // 绘画矩形
            CircleCommand(wordPad).execute() // 绘画圆形
    
    • 1
    • 2
    • 3

    日志输出:

    I/System.out: 绘画了一个矩形
    I/System.out: 绘画了一个圆形
    
    • 1
    • 2

    好了,现在我们需要去存储行为了:

    object Invoker {
        val commandList = mutableListOf()
    
        fun executeCommand(command: ICommand){
            command.execute()
            commandList.add(command)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这样,后续的行为都由 Invoker 代替执行:

            val wordPad = WordPad()
            Invoker.executeCommand(RectCommand(wordPad)) // 绘画矩形
            Invoker.executeCommand(CircleCommand(wordPad)) // 绘画圆形
    
    • 1
    • 2
    • 3

    同时,由于 Invoker 已经存储了这些行为,我那么就可以对这些行为进行管控了,例如重复用户第一次行为,或者重复用户最后一次行为,或者重复用户所有行为:

    object Invoker {
        val commandList = mutableListOf()
    
        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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
            val wordPad = WordPad()
            Invoker.executeCommand(RectCommand(wordPad)) // 绘画矩形
            Invoker.executeCommand(CircleCommand(wordPad)) // 绘画圆形
            println("重复用户全部行为:")
            Invoker.repeat()
            println("重复用户第一次行为:")
            Invoker.doFirst()
            println("重复用户最后一次行为:")
            Invoker.doLast()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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: 绘画了一个圆形
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    误区

    初步看的话,会发现上面讲的命令模式是没什么问题的,确实也是可以这样实现的。但是,它却是有一个很容易让人理解错误的误区,那就是 WordPad。

    因为对于命令模式来说,最核心的问题在于对命令的封装以及管控,所以,即使去掉了 WordPad 也不会有任何影响!

    下面我们把 RectCommand 和 CircleCommand 也相应改下:

    class RectCommand : ICommand {
        override fun execute() {
            println("绘画了一个矩形")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    class CircleCommand : ICommand {
        override fun execute() {
            println("绘画了一个圆形")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    执行代码也改下:

            Invoker.executeCommand(RectCommand()) // 绘画矩形
            Invoker.executeCommand(CircleCommand()) // 绘画圆形
            println("重复用户全部行为:")
            Invoker.repeat()
            println("重复用户第一次行为:")
            Invoker.doFirst()
            println("重复用户最后一次行为:")
            Invoker.doLast()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    会发现跟输出结果并没有什么不同。因为对于 Invoker 而言,它无需知道每个命令的具体执行细节,它只负责调用和记录即可。但是,由于大部分的命令模式的讲解文都会从类似 WordPad 的类进行说起,就会让人感觉,好像命令模式是跟某个工具类进行绑定一样,还可能会感觉,该工具类只能存在一个。

    思考与探索。

  • 相关阅读:
    【K8s入门必看】第二篇 —— 快速部署集群指南
    你的专属音乐生成器「GitHub 热点速览」
    归并排序的非递归实现
    8.反悔贪心
    Splay
    N皇后问题(分支限界法)
    spring cloud新版本使用loadbalancer替代Ribbon
    第59章 ApplicationPart内置依赖注入中间件
    基于PHP+MySQL个人信息管理系统的设计与实现
    《持续交付:发布可靠软件的系统方法》- 读书笔记(八)
  • 原文地址:https://blog.csdn.net/m0_46278918/article/details/125101855