设计模式系列五之命令模式(Command)

什么是命令模式:封装调用

将”请求”封装成对象,以便使用不同的请求、队列、或者日志来参数化其他对象。命令模式也支持可撤销的操作。
–《Head Fist设计模式》

为什么用命令模式

在开发过程中,我们经常需要向某一些对象发送请求,让他们来处理,但是最终由哪一个对象来处理,我们其实并不在意。程序只需要在运行时确定具体哪个对象处理就好了。这就是命令模式可以帮我们解决的问题,应用命令模式,我们可以解除发起请求者和请求接收者的关联,也就是解耦,让对象之间的调用关系更加灵活。

模式结构

命令模式包含的角色如下:

  • Command:抽象命令
  • ConcreteCommand:具体命令
  • Invoker:请求发起者
  • Receiver:请求接收者
  • Client:客户端

类图

命令模式的使用

1
2
3
4
5
6
7
8
9
10
package com.github.archerda.designpattern.command;
/**
* 抽象命令
*
* Create by archerda on 2017/11/13.
*/
public interface Command {
void execute();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.github.archerda.designpattern.command;
/**
* 具体命令: 关灯
*
* Create by archerda on 2017/11/13.
*/
public class LightOffCommand implements Command {
private LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.off();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.github.archerda.designpattern.command;
/**
* 具体命令:开灯
*
* Create by archerda on 2017/11/13.
*/
public class LightOnCommand implements Command {
private LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.on();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.github.archerda.designpattern.command;
/**
* 具体命令:关音响
*
* Create by archerda on 2017/11/13.
*/
public class StereoOffCommand implements Command {
private StereoReceiver stereoReceiver;
public StereoOffCommand(StereoReceiver stereoReceiver) {
this.stereoReceiver = stereoReceiver;
}
@Override
public void execute() {
stereoReceiver.off();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.github.archerda.designpattern.command;
/**
* 具体命令:打开音响并播放CD
*
* Create by archerda on 2017/11/13.
*/
public class StereoOnWithCDCommand implements Command {
private StereoReceiver stereoReceiver;
public StereoOnWithCDCommand(StereoReceiver stereoReceiver) {
this.stereoReceiver = stereoReceiver;
}
@Override
public void execute() {
stereoReceiver.on();
stereoReceiver.setCd();
stereoReceiver.setVolume(11);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.github.archerda.designpattern.command;
/**
* 具体命令:空对象
*
* Create by archerda on 2017/11/13.
*/
public class NoCommand implements Command {
@Override
public void 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
package com.github.archerda.designpattern.command;
/**
* 请求接收者:灯
*
* Create by archerda on 2017/11/13.
*/
public class LightReceiver {
private String name;
public LightReceiver(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Light{" +
"name='" + name + '\'' +
'}';
}
public void on() {
System.out.println(name + " light is on");
}
public void off() {
System.out.println(name + " light is off");
}
}
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
package com.github.archerda.designpattern.command;
/**
* 请求接收者:音响
*
* Create by archerda on 2017/11/13.
*/
public class StereoReceiver {
private String name;
private int volume;
public StereoReceiver(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getVolume() {
return volume;
}
public void setVolume(int volume) {
this.volume = volume;
}
@Override
public String toString() {
return "Stereo{" +
"name='" + name + '\'' +
", volume=" + volume +
'}';
}
public void on() {
System.out.println(name + " stereo is on");
}
public void off() {
System.out.println(name + " stereo is off");
}
public void setCd() {
System.out.println(name + " is set for CD input");
}
public void setDvd() {
System.out.println(name + " is set for DVD input");
}
public void setRadio() {
System.out.println(name + " is set for Radio input");
}
}
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
package com.github.archerda.designpattern.command;
import java.util.Arrays;
/**
* 发起请求者:遥控器
*
* Create by archerda on 2017/11/13.
*/
public class RemoteControlInvoker {
private static final int SLOT_NUM = 7
;
private Command[] onCommands;
private Command[] offCommands;
public RemoteControlInvoker() {
onCommands = new Command[SLOT_NUM];
offCommands = new Command[SLOT_NUM];
for (int i = 0; i < SLOT_NUM; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onBtnWasPushed(int slot) {
onCommands[slot].execute();
}
public void offBtnWasPushed(int slot) {
offCommands[slot].execute();
}
@Override
public String toString() {
return "RemoteControl{" +
"onCommands=" + Arrays.toString(onCommands) +
", offCommands=" + Arrays.toString(offCommands) +
'}';
}
}
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
package com.github.archerda.designpattern.command;
/**
* 客户端
*
* Create by archerda on 2017/11/13.
*/
public class RemoteLoaderClient {
public static void main(String[] args) {
// 创建 Receiver(请求接收者)
LightReceiver livingRoomLightReceiver = new LightReceiver("Living Room");
LightReceiver kitchenRoomLightReceiver = new LightReceiver("Kitchen Room");
StereoReceiver stereoReceiver = new StereoReceiver("Living Room");
// 创建 Command(通过命令将发起请求者和请求接收者解耦)
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLightReceiver);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLightReceiver);
LightOnCommand kitchenRoomLightOn = new LightOnCommand(kitchenRoomLightReceiver);
LightOffCommand kitchenRoomLightOff = new LightOffCommand(kitchenRoomLightReceiver);
StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereoReceiver);
StereoOffCommand stereoOff = new StereoOffCommand(stereoReceiver);
// 创建 Invoker(发起请求者)
RemoteControlInvoker remoteControlInvoker = new RemoteControlInvoker();
remoteControlInvoker.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControlInvoker.setCommand(1, kitchenRoomLightOn, kitchenRoomLightOff);
remoteControlInvoker.setCommand(2, stereoOnWithCD, stereoOff);
// 打开0号开关,至于接下来谁来响应这个动作,Invoker并不关心
// ...
remoteControlInvoker.onBtnWasPushed(0);
remoteControlInvoker.offBtnWasPushed(0);
remoteControlInvoker.onBtnWasPushed(1);
remoteControlInvoker.offBtnWasPushed(1);
remoteControlInvoker.onBtnWasPushed(2);
remoteControlInvoker.offBtnWasPushed(2);
}
}

优点

  1. 将发出请求的对象与执行请求的对象解耦;
  2. 新的命令可以很容易加到系统中;
  3. 可以很容易实现命令队列和宏命令;
  4. 可以方便地实现undo和redo;

缺点

  1. 可能导致系统中有过多的具体命令类,因为需要针对每一个命令设计一个具体命令类(这一点与装饰者模式类似);

应用场景

  1. 需要将请求发起者和请求处理者解耦,使得发起者和处理者不直接交互(或因为它们不能交互);
  2. 系统需要在不同的时间指定请求,将请求排队和处理(例如日志记录系统);
  3. 系统需要支持undo和redo操作;

扩展

宏命令

宏命令又称组合命令,他是命令模式和组合模式的结合。宏命令也是一个具体的命令,不过它包含的不是接收者,而是一组命令对象。在调用宏命令的execute方法时,它会循环去调用每个成员命令的execute方法。执行一个宏命令将可以执行一组命令,从而实现对命令的批量操作。

在JDK中的应用

java.lang.Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.github.archerda.designpattern.command.jdk;
/**
* Create by archerda on 2017/11/15.
*/
public class CommandInThread {
public static void main(String[] args) {
// 创建Command:直接命令(没有Receiver)
Runnable runnable = () -> System.out.println("I am a Receiver");
// 创建一个Invoker:请求发起人
Thread thread = new Thread(runnable);
// 发起请求
thread.start();
}
}

参考文档