行为型——命令模式

概述

所谓“命令模式”,简单解释就是:把命令封装成对象,当客户需要执行某条命令时,直接new一个该命令对应的对象,然后调用命令对象中的excute方法即可。这种设计下,命令调用者无需关心命令具体是怎样执行的,达到命令调用者和命令执行者之间解耦的目的

这里的“命令”指的是什么?

任何逻辑处理请求都可以看作是命令,换句话说,某段逻辑处理代码在当前系统中多次出现,或者不同的参数下需要执行不同的逻辑处理代码,你就可以将这些逻辑处理代码封装成一个个命令对象。

现实生活中有没有类似例子?

前面对“命令模式”的解释可能还有点抽象,现实生活中有没有类似的例子可以对比呢?当然有, 比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。

定义与实现

命令模式比较正式的定义如下: 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

优缺点

命令模式的优缺点如下:

  • 优点
    • 降低耦合度,因为把命令抽象成对象,实现了命令调用者和执行这之间的解耦;
    • 开闭原则,可以方便地扩展、修改、增加、删除命令;
    • 实现命令管理,比如结合“备忘录模式”,实现命令的undo和redo操作。
  • 缺点
    • 类更多,系统更加复杂,同时更难以理解(大部分设计模式的通病)。

类图结构

在“命令模式”中主要涉及三个角色:命令调用者,命令对象和命令执行者。下图是一个比较通用类结构:

宏命令

以“遥控器”为例,有的时候,我们可能希望按一个按钮,同时实现多个功能,这是后就需要用到“宏命令”了。

所谓的宏命令就是:具体命令类中不再拥有单个命令执行者,而是拥有一个命令执行者数组,在execute函数中,用for循环调用每个命令执行者的action方法。

1
2
3
4
5
6
7
8
9
10
11
public class MacroCommand implements Command{
Command[] commands;
public MacroCommand(Command[] commands){
this.commands = commands;
}
public void execute(){
for(int i = 0; i < commands.length; ++i){
commands[i].execute();
}
}
}

命令模式下的“遥控器”

这里我们基于设计模式,实现一个简单的“遥控器”类,并实现打开灯的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//抽象命令类
public interface Command {
void execute();
}
//具体命令类:打开灯
public class LightOnCommand implements Command {
Light light;

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

@Override
public void execute() {
light.on();
}
}
//具体命令类:关闭灯
public class LightOffCommand implements Command {
Light light;

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

@Override
public void execute() {
light.off();
}
}
//命令执行者
public class Light {

public void on() {
System.out.println("Light is on!");
}

public void off() {
System.out.println("Light is off!");
}
}

/**
* 命令调用者:遥控器
*/
public class Invoker {
private Command[] onCommands;
private Command[] offCommands;
private final int slotNum = 7;

public Invoker() {
this.onCommands = new Command[slotNum];
this.offCommands = new Command[slotNum];
}

public void setOnCommand(Command command, int slot) {
onCommands[slot] = command;
}

public void setOffCommand(Command command, int slot) {
offCommands[slot] = command;
}

public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
}

public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
}
}
//客户
public class Client {
public static void main(String[] args) {
Invoker invoker = new Invoker();
Light light = new Light();
//创建命令实例,并指定命令执行者
Command lightOnCommand = new LightOnCommand(light);
Command lightOffCommand = new LightOffCommand(light);
invoker.setOnCommand(lightOnCommand, 0);
invoker.setOffCommand(lightOffCommand, 0);
invoker.onButtonWasPushed(0);
invoker.offButtonWasPushed(0);
}
}

应用

应用场景

当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现。使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令呈现弱耦合(内部方法无需一致),具备良好的扩展性。
命令模式通常适用于以下场景。

  1. 请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
  2. 系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
  3. 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
  4. 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。

在JDK中的应用

  1. JDK中的Runnable接口,相当于命令模式中的抽象命令,其中的“run”方法,相当于这里的“execute”方法。
  2. JUnit中的Test接口,实现该接口,就可以直接进行测试。

在Spring中的应用

  1. 在Jdbc Template里面用到了命令模式,感兴趣的可以自行了解。

参考资料

  1. https://pdai.tech/md/dev-spec/pattern/18_command.html

  2. http://c.biancheng.net/view/1380.html

  3. 《HeadFirst设计模式》

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2022 Yin Peng
  • 引擎: Hexo   |  主题:修改自 Ayer
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信