转载

命令模式(Command Pattern)

温馨提示:
本文最后更新于 2023年02月13日,已超过 670 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

命令模式

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。

前言

今天一大早就来了图书馆🏫,刚坐下来就迫不及待的开始看命令模式的相关资料📚。不过这个模式跟我之前的理解出入特别大。

最开始的时候,我以为的命令模式就是函数回调。但后来发现并不是,但他们两个确实是有关系,这一切的答案都藏在 GOF 的设计模式一书中。

开始学习

在软件设计模式之始 GOF 的原著中,命令模式的讲解还是在他们开发的那个编辑工具中,其用来讲解的案例就是我们日常编辑使用的编辑工具中,在工具栏有很多个功能按钮,或者菜单按钮。 就比如编辑工具中的一个 新增文件 的按钮🆕吧。GOF 要表达的意思就是,这个 新增文件 对系统本身来讲就是给使用者提供的一个命令,我们在用的过程中可以给编辑器发送不同的命令,但是这个 新增文件 的操作并不是在这个按钮上实现的,同时对于我们发送命令的人来说,也不知道具体这个 新增文件 这个动作是由谁来执行、怎么执行,这对我们来讲完全是透明的。

我们先不讨论这样做的好处,先看下这里面要说的几个角色

  1. 客户端应用
  2. 新增文件按钮(调用新增文件操作命令)
  3. 操作命令
  4. 操作接收(负责具体的操作执行)

我试着按照这个结构写了一下这个代码

public class Client {
    public static void main(String[] args) {
        FileReceiver fileReceiver = new FileReceiver();
        AddFileCommand addFileCommand = new AddFileCommand(fileReceiver);
        Invoker invoker = new Invoker();
        invoker.setCommand(addFileCommand);
        invoker.executeCommand();
    }
}
  1. 客户端应用 Client
  2. 新增文件按钮(调用新增文件操作)Invoker
  3. 操作命令 AddFileCommand
  4. 操作接收者 FileReceiver
新增文件

关于 ‘命令’ 的疑惑 🤔

按照上面的方式实现下来,我有一种感觉,有种脱裤子放屁的感觉,我直接调用 FileReceiver 不香吗

非要这样

command.png

我以为,使用者利用按钮直接调用对应的操作不就行了吗?就像我下面这样中间非要放一个命令对象(将具体的请求包装成了这个对象)?

command-no-command

解惑 ‘命令’ 🤪

不过不久我就找到了答案💡

首先看下命令模式要解决的问题❔:对请求排队、下载或记录请求日志,以及支持可撤消的操作。

然后我们开始思考🤔如果没有中间这个 “命令” 角色,那这些功能做在哪里?只能做在接收者,也就是逻辑具体的实现里面,那这是不是违背了一个设计原则,叫做 单一职责原则 ?而且对这种 ”辅助型“ 的功能变多会导致逻辑实现类变得越来越”肿胀“,没错,就是”肿胀“!

并且这也使得调用者和实现者之间通过这个“命令”进行解耦,然后我们使用依赖倒置原则,将“命令”提取出来一个抽象类,这使得扩展请求也变得容易了。而且对于高层模块来说,自己完全不需要关心调用的时候具体的请求内容和实现内容,通过“命令”来完成自己的操作,比如点一个按钮、遥控器下的按键(从这里还可以看出,多个命令可以对应一个接受者,比如数字键的换台)、去餐厅点菜。这样一看,命令模式还真是符合这种设计思路的命名啊。

命令模式类图 📌

command-UML

主要结构

  1. 调用者,也是暴露给客户端的对象 Invoker
  2. 命令接口,Command(满足依赖倒置原则,便于扩展)
  3. 具体的命令,这里要包含谁来接受这个命令的接受者对象 ConcreteCommand
  4. 命令的接收者,这里没有列实现类是因为任何类都可以是接收者 Receiver

代码 📃

命令模式这篇使用的是通用框架写了一个实现,在这基础上事实上我们可以做很多扩展,比如再 Invoker 类中将 command 换成 List<Command> 来实现请求的排队、撤销等操作。

image-20201128142121299

总结 📚

适用场景:

  1. 需要记录请求记录;
  2. 请求可以进行排队处理;
  3. 请求可以进行撤销、重做;
  4. 具体接收者来决定请求是否执行(关于这一点,如果请求不是封装成一个对象的话,判断起来是比较困难的)

不过这种模式并不是一个常用的思想,一定是当你想要对请求做一些事情的时候才考虑,具体的事情就上面提到的 4 点,不然的话使用这种模式真的就是我上面说的,“脱裤子放屁了”。

最后再来一句话来总结一下命令模式,“张三,把门关一下”。这里我就是 Invoker,“把门关一下“ 就是 command (命令),“张三” 是 receiver (接收者)。更多时候,我们实际开发中,”把门关一下“ 都是定义好的,”我“直接选就行了,就像遥控器上的按键一样。但切记这个模式的使用时机,别做”恶心“人的事!

如果哪里有问题或者有疑问,欢迎加我微信(lvgocc)讨论,或者直接进群交流!天凉了🥶🥶,进群一起取暖也好啊😁,等你~

其他设计模式:点击查看

正文到此结束
本文目录