《Head First Design Patterns》:第6章,命令(Command)模式

命令模式的定义

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

场景

家电自动化遥控器

场景描述

遥控器具有7个可编程的插槽,其中每个插槽都可以指定到不同的家电装置(如电灯、风扇、热水器、音响设备、等等),并且每个插槽都有对应的开关按钮。遥控器还具有一个整体的撤销按钮。现在,需要设计一组控制遥控器的API,让每个插槽都能够控制一个或一组装置(备注,需要能够控制目前的设备和任务未来可能出现的设备)。

下图是一些已知家电设备厂商的API:

方案

使用命令模式将动作的请求者和动作的执行者对象中解藕。请求者为遥控器,执行者对象是家电设备,利用命令对象,把请求(例如,打开电灯)封装成一个特定对象(例如,客厅电灯对象)。所以,如果每个按钮都存储一个命令对象,那么当按钮被按下的时候,就可以请命令对象做相关的工作。遥控器并不需要知道工作内容是什么,只要有一个命令对象能和正确的对象沟通,把事情做好就可以了。这样,遥控器和电灯对象就解藕了。

1
2
3
4
public interface Command {
public void execute();
public void undo();
}
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
public class LightOnCommand implements Command {
Light light;

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

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

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

public class LightOffCommand implements Command {
Light light;

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

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

public void undo() {
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;

public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}

public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = onCommands[slot];
}

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

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

方案的更进一步

利用宏命令,实现一个按钮同时控制多个灯光、音箱、电视、DVD、热水器的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
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();
}
}
}