设计模式之命令模式

设计模式之命令模式

第一个命令对象

实现命令接口

1
2
3
4
5
6
7
8
9
10
11
/**
* 命令接口
* @author Mr.zxb
* @date 2020-08-16 20:42:17
*/
public interface Command {
/**
* 执行命令接口
*/
void execute();
}

实现一个打开电灯的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 电灯类
* @author Mr.zxb
* @date 2020-08-16 20:44:01
*/
public class Light {

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

public void off() {
System.out.println("Light is off");
}
}z
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 打开电灯的命令
* @author Mr.zxb
* @date 2020-08-16 20:43:31
*/
public class LightOnCommand implements Command {

private Light light;

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

@Override
public void execute() {
light.on();
}
}

创建一个简单遥控器

设计一个遥控器对象,它只有一个按钮和对应的插槽,可以控制一个简单装置

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
/**
* 简单的遥控器
* @author Mr.zxb
* @date 2020-08-16 20:47:25
*/
public class SimpleRemoteControl {
/**
* 一个插槽
*/
private Command slot;

/**
* 设置插槽控制的命令
* @param command
*/
public void setCommand(Command command) {
slot = command;
}

/**
* 按下按钮的功能
*/
public void buttonWasPressed() {
slot.execute();
}
}

遥控器使用的简单测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 遥控器简单测试
* @author Mr.zxb
* @date 2020-08-16 20:49:47
*/
public class RemoteControlTest {
public static void main(String[] args) {
// 实例化一个简单遥控器装置
SimpleRemoteControl remote = new SimpleRemoteControl();
// 创建一个电灯
Light light = new Light();
// 创建一个命令
LightOnCommand lightOnCommand = new LightOnCommand(light);

// 把命令传给调用者
remote.setCommand(lightOnCommand);
// 执行命令
remote.buttonWasPressed();
}
}

定义命令模式

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

定义命令模式类图

实现一个复杂遥控器

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
/**
* 遥控器
* @author Mr.zxb
* @date 2020-08-16 20:59:49
*/
public class RemoteControl {
/**
* 一组打开命令
*/
private final Command[] onCommands;
/**
* 一组关闭命令
*/
private final Command[] offCommands;
/**
* 撤销命令
*/
private Command undoCommand;

public RemoteControl() {
this.onCommands = new Command[7];
this.offCommands = new Command[7];

// 预先设置无命令的Command
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
this.onCommands[i] = noCommand;
this.offCommands[i] = noCommand;
}
this.undoCommand = noCommand;
}

/**
* 指定插槽设置开和关命令
* @param slot
* @param onCommand
* @param offCommand
*/
public void setCommand(int slot, Command onCommand, Command offCommand) {
this.onCommands[slot] = onCommand;
this.offCommands[slot] = offCommand;
}

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

public void offButtonWasPushed(int slot) {
this.offCommands[slot].execute();
this.undoCommand = offCommands[slot];
}

public void undoButtonWasPushed() {
this.undoCommand.undo();
}

@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("\n------- Remote Control-------\n");
for (int i = 0; i < this.onCommands.length; i++) {
buffer.append("[slot ")
.append(i)
.append(" ]")
.append(this.onCommands[i].getClass().getName())
.append(" ")
.append(this.offCommands[i].getClass().getName())
.append("\n");
}
return buffer.toString();
}
}

实现其他命令

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/**
* 打开电灯的命令
* @author Mr.zxb
* @date 2020-08-16 20:43:31
*/
public class LightOnCommand implements Command {

private Light light;

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

@Override
public void execute() {
light.on();
}

@Override
public void undo() {
light.off();
}
}

/**
* 关闭电灯的命令
* @author Mr.zxb
* @date 2020-08-16 20:43:31
*/
public class LightOffCommand implements Command {

private Light light;

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

@Override
public void execute() {
light.off();
}

@Override
public void undo() {
light.on();
}
}

/**
* 音响类
* @author Mr.zxb
* @date 2020-08-16 21:09:29
*/
public class Stereo {

private String name;

public Stereo(String name) {
this.name = name;
}

public void on() {
System.out.printf("%s stereo is on\n", name);
}

public void off() {
System.out.printf("%s stereo is off\n", name);
}

public void setCD() {
System.out.printf("%s stereo is et for CD input\n", name);
}

public void setVolume(int volume) {
System.out.printf("%s stereo volume se to 11\n", name);
}
}

/**
* 音响命令对象
* @author Mr.zxb
* @date 2020-08-16 21:09:13
*/
public class StereoOnWithCDCommand implements Command {

private Stereo stereo;

public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}

@Override
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}

@Override
public void undo() {
stereo.off();
}
}

/**
* 音响命令对象
* @author Mr.zxb
* @date 2020-08-16 21:09:13
*/
public class StereoOffWithCDCommand implements Command {

private Stereo stereo;

public StereoOffWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}

@Override
public void execute() {
stereo.off();
}

@Override
public void undo() {
stereo.on();
}
}

测试我们的遥控器

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
public class RemoteLoader {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();

// 将所有装置创建在合适的位置
Light livingRoomLight = new Light("Living Room");
Light kitchenLight = new Light("Kitchen");
Stereo stereo = new Stereo("Living Room");

// 创建所有电灯命令对象
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOnCommand kitchenLightLightOn = new LightOnCommand(kitchenLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
LightOffCommand kitchenLightLightOff = new LightOffCommand(kitchenLight);

// 创建音响的开与关命令
StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
StereoOffWithCDCommand stereoOffWithCD = new StereoOffWithCDCommand(stereo);

// 将各命令装置在指定的插槽
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightLightOn, kitchenLightLightOff);
remoteControl.setCommand(2, stereoOnWithCD, stereoOffWithCD);
System.out.println(remoteControl);

// 执行遥控器的按钮
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.undoButtonWasPushed();

remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.undoButtonWasPushed();

remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
remoteControl.undoButtonWasPushed();
}
}

使用宏命令

每个遥控器都应该具备“Party模式”

  • 先创建想要进入宏的命令集合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建所有的装置,电灯、电视、音响和热水器
Light light = new Light("Living Room");
TV tv = new TV("Living Room");
Stereo stereo = new Stereo("Living Room");
Hottub hottub = new Hottub();
// 创建所有的On来控制它们
LightOnCommand lightOn = new LightOnCommand(light);
StereoOnCommand sterenOn = new StereoOnCommand(stereo);
TVOnCommand tvOn = new TVOnCommand(tv);
HottubOnCommand hottubOn = new HottubOnCommand(hottub);

// 创建所有的off来控制它们
LightOffCommand lightOff = new LightOffCommand(light);
StereoOffCommand sterenOff = new StereoOffCommand(stereo);
TVOffCommand tvOff = new TVOnCffmmand(tv);
HottubOffCommand hottubOff = new HottubOnCffmmand(hottub);
  • 接下来创建两个数组,其中记录开启命令,另一个记录关闭命令,并在数组内放入对应的命令
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
/**
* 宏命令,一组命令
* @author Mr.zxb
* @date 2020-08-16 21:45:21
*/
public class MacroCommand implements Command {

private final Command[] commands;

public MacroCommand(Command[] commands) {
this.commands = commands;
}

@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}

@Override
public void undo() {
for (Command command : commands) {
command.undo();
}
}
}
1
2
3
4
5
6
7
// 数组用来记录开启命令,另一个数组用来记录关闭命令
Command[] partyOn = { lightOn, stereoOn, tvOn, hottubOn };
Command[] partyOff = { lightOff, sterenOff, tvOff, hottubOff };

// 创建对应的宏持有它们
MacroCommand partyOnMacro = new MacroCommand(partyOn);
MacroCommand partyOffMacro = new MacroCommand(partyOff);
  • 然后将宏命令指定给我们所希望的按钮:
1
2
// 将宏命令指定给一个按钮
remoteControl.setCommand(4, partyOnMacro, partyOffMacro);
  • 最后,只需按下一些按钮,测试即可
1
2
3
4
5
System.out.println(remoteControl);
System.out.println("--- Pushing Macro On ---");
remoteControl.onButtonWasPushed(4);
System.out.println("--- Pushing Macro Off ---");
remoteControl.offButtonWasPushed(4);

总结

  1. 命令模式将发出请求的对象和执行请求的对象解耦
  2. 在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一个或一组动作
  3. 调用者通过命令对象的execute()发出请求,这会使得接收者的动作被调用
  4. 调用者可以接受命令当做参数,甚至在运行时动态地进行
  5. 命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行的状态
  6. 宏命令是命令的一种简单的延伸,允许调用多个命令。宏方法也可以支持撤销
  7. 实际操作时,很常见使用“聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接收者
  8. 命令也可以用来实习日志和事务系统