1. 命名模式概述
概念
- 将一个请求封装为一个对象,从而使可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作
- 命令模式可以用于请求排队:只需创建一个命令队列,将每个需要执行的命令一次传入队列中,然后工作线程不断地从命令队列中取出队列头的命令,再执行命令即可。实际上,Android app 的界面就是这么实现的
优点
- 降低系统的耦合度。将“行为请求者”和“行为实现者”解耦
- 扩展性强。增加或删除命令非常方便,并且不会影响其他类
- 封装“方法调用”,方便实现 Undo 和 Redo 操作
- 灵活性强,可以实现宏命令(组合多个命令形成的宏大的命令)
缺点
- 会产生大量的命令类,增加了系统的复杂性。设计模式的工作常常是将功能细化,很容易造成类膨胀,在命令模式中体现得尤其明显
- 实际上使用设计模式需要慎重,在确实有必要的情况下再使用设计模式。作为一名合格的程序员,心中始终要将产品放在第一位,代码的易读性比炫技更重要
2. 命令模式 Demo
大门类
1
2
3
4
5
6
7
8
9public class Door {
public void openDoor() {
System.out.println("门打开了");
}
public void closeDoor() {
System.out.println("门关闭了");
}
}电灯类
1
2
3
4
5
6
7
8
9public class Light {
public void lightOn() {
System.out.println("打开了电灯");
}
public void lightOff() {
System.out.println("关闭了电灯");
}
}电视类
1
2
3
4
5
6
7
8
9public class Tv {
public void TurnOnTv() {
System.out.println("电视打开了");
}
public void TurnOffTv() {
System.out.println("电视关闭了");
}
}音乐类
1
2
3
4
5
6
7
8
9public class Music {
public void play() {
System.out.println("开始播放音乐");
}
public void stop() {
System.out.println("停止播放音乐");
}
}
万能遥控器 1.0:功能单一
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// 初始化开关
Switch switchDoor = 省略绑定UI代码;
Switch switchLight = 省略绑定UI代码;
Switch switchTv = 省略绑定UI代码;
Switch switchMusic = 省略绑定UI代码;
// 初始化智能家居
Door door = new Door();
Light light = new Light();
Tv tv = new Tv();
Music music = new Music();
// 大门开关遥控
switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
door.openDoor();
} else {
door.closeDoor();
}
});
// 电灯开关遥控
switchLight.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
light.lightOn();
} else {
light.lightOff();
}
});
// 电视开关遥控
switchTv.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
tv.TurnOnTv();
} else {
tv.TurnOffTv();
}
});
// 音乐开关遥控
switchMusic.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
music.play();
} else {
music.stop();
}
});万能遥控器 2.0:增加撤销功能
设计一个枚举类 Operation,代表每一步的操作
1
2
3
4
5
6
7
8
9
10public enum Operation {
DOOR_OPEN,
DOOR_CLOSE,
LIGHT_ON,
LIGHT_OFF,
TV_TURN_ON,
TV_TURN_OFF,
MUSIC_PLAY,
MUSIC_STOP
}在客户端定义枚举类型变量 lastOperation,实现撤销一步操作
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
66public class Client {
// 上一步的操作
Operation lastOperation;
protected void test() {
// 初始化开关和撤销按钮
Switch switchDoor = 省略绑定UI代码;
Switch switchLight = 省略绑定UI代码;
Switch switchTv = 省略绑定UI代码;
Switch switchMusic = 省略绑定UI代码;
Button btnUndo = 省略绑定UI代码;
// 初始化智能家居
Door door = new Door();
Light light = new Light();
Tv tv = new Tv();
Music music = new Music();
// 大门开关遥控
switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
lastOperation = Operation.DOOR_OPEN;
door.openDoor();
} else {
lastOperation = Operation.DOOR_CLOSE;
door.closeDoor();
}
});
// 电灯开关遥控
switchLight.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
lastOperation = Operation.LIGHT_ON;
light.lightOn();
} else {
lastOperation = Operation.LIGHT_OFF;
light.lightOff();
}
});
... 电视、音乐类似
btnUndo.setOnClickListener(view -> {
if (lastOperation == null) return;
// 撤销上一步
switch (lastOperation) {
case DOOR_OPEN:
door.closeDoor();
break;
case DOOR_CLOSE:
door.openDoor();
break;
case LIGHT_ON:
light.lightOff();
break;
case LIGHT_OFF:
light.lightOn();
break;
... 电视、音乐类似
}
});
}
}优化:使用栈数据结构实现撤销多步(缺点:代码越来越臃肿,可读性和可扩展性变差)
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
69public class Client {
// 所有的操作
Stack<Operation> operations = new Stack<>();
@Test
protected void test() {
// 初始化开关和撤销按钮
Switch switchDoor = 省略绑定UI代码;
Switch switchLight = 省略绑定UI代码;
Switch switchTv = 省略绑定UI代码;
Switch switchMusic = 省略绑定UI代码;
Button btnUndo = 省略绑定UI代码;
// 初始化智能家居
Door door = new Door();
Light light = new Light();
Tv tv = new Tv();
Music music = new Music();
// 大门开关遥控
switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
operations.push(Operation.DOOR_OPEN);
door.openDoor();
} else {
operations.push(Operation.DOOR_CLOSE);
door.closeDoor();
}
});
// 电灯开关遥控
switchLight.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
operations.push(Operation.LIGHT_ON);
light.lightOn();
} else {
operations.push(Operation.LIGHT_OFF);
light.lightOff();
}
});
...电视、音乐类似
// 撤销按钮
btnUndo.setOnClickListener(view -> {
if (operations.isEmpty()) return;
// 弹出栈顶的上一步操作
Operation lastOperation = operations.pop();
// 撤销上一步
switch (lastOperation) {
case DOOR_OPEN:
door.closeDoor();
break;
case DOOR_CLOSE:
door.openDoor();
break;
case LIGHT_ON:
light.lightOff();
break;
case LIGHT_OFF:
light.lightOn();
break;
...电视、音乐类似
}
});
}
}
万能遥控器 3.0:命令模式,让遥控器不需要知道家居的接口,它只需要负责监听用户按下开关,再根据开关状态发出正确的命令,对应的家居在收到命名后做出响应,达到将“行为请求者”和“行为实现者” 解耦的目的
定义一个命令接口
1
2
3public interface ICommand {
void execute();
}开门命令
1
2
3
4
5
6
7
8
9
10
11
12public class DoorOpenCommand implements ICommand {
private Door door;
public void setDoor(Door door) {
this.door = door;
}
public void execute() {
door.openDoor();
}
}关门命令
1
2
3
4
5
6
7
8
9
10
11
12
13public class DoorCloseCommand implements ICommand {
private Door door;
public void setDoor(Door door) {
this.door = door;
}
public void execute() {
door.closeDoor();
}
}开灯命令
1
2
3
4
5
6
7
8
9
10
11
12
13public class LightOnCommand implements ICommand {
Light light;
public void setLight(Light light) {
this.light = light;
}
public void execute() {
light.lightOn();
}
}关灯命令
1
2
3
4
5
6
7
8
9
10
11
12
13public class LightOffCommand implements ICommand {
Light light;
public void setLight(Light light) {
this.light = light;
}
public void execute() {
light.lightOff();
}
}客户端:将家居控制的代码转移到了命令类中,当命令执行时,调用对应家具的 API 实现开启或关闭
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// 初始化命令
DoorOpenCommand doorOpenCommand = new DoorOpenCommand();
DoorCloseCommand doorCloseCommand = new DoorCloseCommand();
doorOpenCommand.setDoor(door);
doorCloseCommand.setDoor(door);
LightOnCommand lightOnCommand = new LightOnCommand();
LightOffCommand lightOffCommand = new LightOffCommand();
lightOnCommand.setLight(light);
lightOffCommand.setLight(light);
...电视、音乐类似
// 大门开关遥控
switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
doorOpenCommand.execute();
} else {
doorCloseCommand.execute();
}
});
// 电灯开关遥控
switchLight.setOnCheckedChangeListener((view, isChecked) -> {
if (isChecked) {
lightOnCommand.execute();
} else {
lightOffCommand.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
32public class Client {
@Test
protected void test() {
...初始化
// 大门开关遥控
switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
handleCommand(isChecked, doorOpenCommand, doorCloseCommand);
});
// 电灯开关遥控
switchLight.setOnCheckedChangeListener((view, isChecked) -> {
handleCommand(isChecked, lightOnCommand, lightOffCommand);
});
// 电视开关遥控
switchTv.setOnCheckedChangeListener((view, isChecked) -> {
handleCommand(isChecked, turnOnTvCommand, turnOffTvCommand);
});
// 音乐开关遥控
switchMusic.setOnCheckedChangeListener((view, isChecked) -> {
handleCommand(isChecked, musicPlayCommand, musicStopCommand);
});
}
private void handleCommand(boolean isChecked, ICommand openCommand, ICommand closeCommand) {
if (isChecked) {
openCommand.execute();
} else {
closeCommand.execute();
}
}
}使用命令模式实现撤销功能:在命令接口中新增 undo 方法
1
2
3
4
5
6public interface ICommand {
void execute();
void undo();
}开门命令中新增 undo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class DoorOpenCommand implements ICommand {
private Door door;
public void setDoor(Door door) {
this.door = door;
}
public void execute() {
door.openDoor();
}
public void undo() {
door.closeDoor();
}
}关门命令中新增 undo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class DoorCloseCommand implements ICommand {
private Door door;
public void setDoor(Door door) {
this.door = door;
}
public void execute() {
door.closeDoor();
}
public void undo() {
door.openDoor();
}
}开灯命令中新增 undo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class LightOnCommand implements ICommand {
Light light;
public void setLight(Light light) {
this.light = light;
}
public void execute() {
light.lightOn();
}
public void undo() {
light.lightOff();
}
}关灯命令中农新增 undo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class LightOffCommand implements ICommand {
Light light;
public void setLight(Light light) {
this.light = light;
}
public void execute() {
light.lightOff();
}
public void undo() {
light.lightOn();
}
}客户端
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
45public class Client {
// 所有的命令
Stack<ICommand> commands = new Stack<>();
@Test
protected void test() {
...初始化
// 大门开关遥控
switchDoor.setOnCheckedChangeListener((view, isChecked) -> {
handleCommand(isChecked, doorOpenCommand, doorCloseCommand);
});
// 电灯开关遥控
switchLight.setOnCheckedChangeListener((view, isChecked) -> {
handleCommand(isChecked, lightOnCommand, lightOffCommand);
});
// 电视开关遥控
switchTv.setOnCheckedChangeListener((view, isChecked) -> {
handleCommand(isChecked, turnOnTvCommand, turnOffTvCommand);
});
// 音乐开关遥控
switchMusic.setOnCheckedChangeListener((view, isChecked) -> {
handleCommand(isChecked, musicPlayCommand, musicStopCommand);
});
// 撤销按钮
btnUndo.setOnClickListener(view -> {
if (commands.isEmpty()) return;
// 撤销上一个命令
ICommand lastCommand = commands.pop();
lastCommand.undo();
});
}
private void handleCommand(boolean isChecked, ICommand openCommand, ICommand closeCommand) {
if (isChecked) {
commands.push(openCommand);
openCommand.execute();
} else {
commands.push(closeCommand);
closeCommand.execute();
}
}
}使用宏命令:给遥控器添加一个“睡眠”按钮,按下时可以一键执行关闭大门、关闭电灯、关闭电视、打开音乐(听着音乐睡觉,优雅)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class MacroCommand implements ICommand {
// 定义一组命令
List<ICommand> commands;
public MacroCommand(List<ICommand> commands) {
this.commands = commands;
}
@Override
public void execute() {
// 宏命令执行时,每个命令依次执行
for (int i = 0; i < commands.size(); i++) {
commands.get(i).execute();
}
}
@Override
public void undo() {
// 宏命令撤销时,每个命令依次撤销
for (int i = 0; i < commands.size(); i++) {
commands.get(i).undo();
}
}
}使用宏命令的客户端:可以任意组合多个命令,并且完全不会增加程序结构的复杂度
1
2
3
4
5
6
7
8
9// 定义睡眠宏命令
MacroCommand sleepCommand = new MacroCommand(Arrays.asList(doorCloseCommand, lightOffCommand, turnOffTvCommand, musicPlayCommand));
// 睡眠按钮
btnSleep.setOnClickListener(view -> {
// 将执行的命令保存到栈中,以便撤销
commands.push(sleepCommand);
// 执行睡眠命令
sleepCommand.execute();
});