设计模式-命令模式

149 阅读5分钟

作用

把方法调用封装起来,当需要将发出请求的对象与执行请求的对象解耦时,使用命令模式。将请求封装成对象,可以让你自由的使用不同的请求,队列,支持将命令组装。

角色

在命令模式中,有这个几个角色是构成此模式的重点

  • Client:客户,用来发起请求,创建具体的Command对象,并在其构造方法中设置接收者
  • Command:命令对象,一般先定义Command接口,具体实现继承Command接口
  • ConcreteCommand:命令的具体实现,在对象中需要注入接收者,执行execute方法时调用接收者的实际执行方法
  • Invoker:调用者,用来将命令从客户传递给接收者的中间类,提供setCommand方法接收命令,并调用Command.execute方法发起请求
  • Receiver:接收者,真正的动作执行者

类图

image.png

类比助记

结合Head first中的例子,来理解记忆

  • 顾客到餐厅来点餐 :客户负责创建命令对象,命令对象包含了包含了接受者上的一组动作,createCommandObject()
  • 服务员过来等待客人点餐:创建Invoker对象,等待Client创建命令对象
  • 在菜单上写下想吃的菜:调用Invoker.setCommand()方法给Invoker对象的Command属性赋值
  • 服务员拿走订单并通知后厨:Invoker 对象实际调用command.execute()方法
  • 厨师接到通知开始炒菜:Receiver对象收到Invoker的调用请求,开始执行action()方法,实际的逻辑操作

通过以上五步就完成了命令模式的整个执行过程

使用方式

这里介绍命令模式一般可以做什么事情

1. 单个的命令操作

命令模式最简单的实现,比如灯泡有开和关两种状态

那么就可以定义两个命令LightOnCommand,LightOffCommand 类

调用时直接调用命令对象的execute()方法即可

2. 宏命令

当单个的命令不再满足你的要求时,可以使用宏命令来解决

比如现在的智能家居,你到家之后不仅需要开灯,还要打开空调,电脑,窗帘等等设备

实现起来也很简单,就是将已经定义好的多个Command实现类组装成一个集合

这里调用者中的command属性就变成了command[]集合了

然后重新定义一个Command对象,execute方法中遍历执行上一步定义好的集合

这回再调用时就可以调用一个Command对象来执行多个操作步骤

3. 对列请求

这个也很好理解,就是你有很多个命令组成队列

使用线程池来执行队列中的命令,来多线程执行

4. 日志请求

这里就需要改造下Command接口了,加上store()和load()方法用来保存和加载

使用store()命令都保存在日志或者磁盘中,当执行过程中系统死机恢复后,调用load()方法来重新加载这一批命令并重新执行

这个要么全部执行,要么都不执行的操作再事物中也有使用

5.撤销操作

命令模式支持撤销操作,具体如下

在命令接口中增加undo()方法

并在调用者对象中增加undoCommand属性用来记录前一次命令

当调用撤销时,调用者紧接着调用undoCommand的undo()方法

延伸:这里的撤销操作实际上也是一种备忘录模式,可见设计模式之间也是互相渗透,重点在与思想,而非手法

要点

  • 命令模式将发出请求的对象和执行者解耦
  • 再被解耦的两者之间时通过命令对象来进行沟通的,命令对象封装了接收者和一个或一组动作
  • 调用者通过调用命令对象的execute()发出请求,这会是的接受者的动作被调用
  • 调用者可以接受命令参数,甚至再运行时动态的进行
  • 命令可以支持撤销,做法是实现一个undo()方法来回到execute执行前的状态
  • 宏命令是命令的一种简单的延伸,允许调用多个命令。宏方法也支持撤销
  • 实际操作中,很常见使用“聪明”命令对象(再execute方法中不光是调用,也有一些逻辑),也就是直接实现了请求,而不是将工作委托给接收者
  • 命令也可以用来实现日志和事物

示例代码

  1. 创建抽象命令类
public interface Command {
    void execute();
}
  1. 创建Reciver
public class Light {
    public void on(){
        System.out.println("light on");
    }

    public void off() {
        System.out.println("light off");
    }
}
  1. 创建命令实现类,电灯类有开关两个操作,所以创建两个命令实现类,这里将Reveiver作为命令参数
public class LightOffCommand implements Command{
    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }
}
public class LightOnCommand implements Command {
    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}
  1. 创建Invoker,也就是服务员,buttonWasPressed方法就是调用者的调用操作
public class SimpleRemoteControl {
    Command slot;

    public SimpleRemoteControl() {}

    public void setCommand(Command command) {
        slot = command;
    }

    public void buttonWasPressed() {
        slot.execute();
    }
}
  1. 简单实现一个开灯操作
public static void main(String[] args) {
    // 调用者-服务员
    SimpleRemoteControl control = new SimpleRemoteControl();
    // 接收者,实际实现功能的人-厨师
    Light light = new Light();
    // 命令对象-顾客点餐,创建命令对象,并加入接受者
    LightOnCommand order = new LightOnCommand(light);
    // 将命令传给调用者-顾客将订单给服务员
    control.setCommand(order);
    // 调用者请求接受者-服务员让厨师炒菜
    control.buttonWasPressed();
}
  1. 运行结果

image.png