《Head First 设计模式》笔记6

命令模式(Command)

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

栗子

现在有个万能遥控器,它有五个插槽和五对开关按钮。每个插槽可以插一张存储卡,存储卡里面存的是可以控制的某个电器代码,对应的开关按钮可以控制某个电器开关。(听起来这个遥控器有点奇怪是不是?你把它想像成小霸王游戏机就可以了)

万能遥控器

你的任务就是给遥控器上的这些开关按钮编程,让它们可以使用存储卡存的电器命令进而控制电器。

下面是其中一些存储卡的代码:

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
// 灯
class Light {
public void on() { }

public void off() { }
}

// 电视
class TV {
public void on() { }

public void off() { }

// 设置频道
public void setInputChannel() { }

// 设置音量
public void setVolume() { }
}

// 安全控制系统
class SecurityControl {
// 警报
public void arm() { }

// 解除警报
public void disarm() { }
}

// ...

当然现在的遥控器雏形还比较简单,只能用到开关命令,但是以后还会升级、增加更多按钮让它可以把那些控制命令都用上,需要控制的电器也会越来越多。

如何满足需求

在这里,遥控器是“动作的请求者”,而那些“动作的执行者”就是存储卡上的电器对象。遥控器不需要知道这些电器对象是怎么执行的开关操作,只需要执行调用它们的命令就行了。

上面这句话有点绕口,用宏命令来解释就是:虽然上面的存储卡代码很简单,开关只有一个 on 和 off 方法,但不是所有电器都封装的这么完美。有些电器的开关操作可能很复杂(厂家没封装好,或者为了更开放),比如先打开风扇电源,再打开主机电源,可能需要一系列操作才能“开启”,而命令模式可以把这些都封装在“开启”这个执行方法中,我们只要点击这个开启,就会完成这一系列已经设置好的操作。

命令接口

1
2
3
interface Command {
void execute();
}

一个打开灯的命令

1
2
3
4
5
6
7
8
9
10
11
12
class LightOnCommand implements Command {
Light light;

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

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

可以控制电器的简单遥控器

这里为了方便说明,先把五个插槽简略成了一个,按钮的按下动作也被简略成了开关按钮中的一个。
(完全版本就是把 Command slot 改成 Command[] slot 数组形式,这里就不实现了)

1
2
3
4
5
6
7
8
9
10
11
class SimpleRemoteControl {
private Command slot;

public void setCommand(Command command) {
this.slot = command;
}

public void buttonWasPressed() {
this.slot.execute();
}
}

模拟遥控器按下按钮

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
SimpleRemoteControl control = new SimpleRemoteControl();

Light light = new Light();
// 开灯命令的接收者被设置为某一个灯
LightOnCommand lightOn = new LightOnCommand(light);

// 设置插槽的命令为开灯
control.setCommand(lightOn);
// 按下开的按钮
control.buttonWasPressed();
}

队列请求

假设有一个工作队列:你控制一端,线程控制另一端。你控制的一端可以添加命令,线程控制的一端则进行这样的动作:从队列取出一个命令,调用它的 execute 方法,等待这个调用完成,然后将该命令丢弃,再取出下一个命令……

工作队列类和进行计算的对象之间是完全解耦的,此刻线程可能在进行财务计算,下一刻可能就在读取网络数据。工作队列不在乎到底做什么,怎么做,它们只知道取出命令对象,然后调用它的 execute 方法。

日志请求

类似宏命令,每次执行某一个命令后,下一步就是 store 保存这些命令操作日志,也就是操作点,这样可以在系统出问题时,方便我们查找操作点。

Author

Zoctan

Posted on

2018-04-02

Updated on

2023-03-14

Licensed under