第七章 命令模式
1.1 引言
为了介绍命令模式,引入如下场景:
设计一个家电自动化遥控器的API。这个遥控器具有七个可编程的插槽(每个都可以指定到一个不同的家电装置),每个插槽都有对应的开关按钮,这个遥控器还具备一个整体的撤销按钮。遥控器的硬件结构如下图所示。
遥控器对象与家电装置的关系?
当遥控器对象的开关按钮被按下,对应的家电装置会被操控,因此,在逻辑上,遥控器对象应关联具体的家电装置。
而对应的家电装置是变化的,可能新增、减少和更换,当遥控器对象操作这些家电装置的逻辑代码是变化的。
当遥控器对象的开关按钮被按下,如果遥控中的控制逻辑代码的设计如下:
if slot1 == Litht,
then light on(),
else if slot1 == Hottub
then hottob.jetsOn().
这样的设计,遥控器对象和具体的家电对象具有严重的耦合性!!!
因此,需要将遥控器对象从具体的家电对象中解耦。这里的遥控器对象可抽象称“动作的请求者”,而家电对象可抽象称“动作的执行者”,也就是说,需要将动作的请求者从动作的执行者中解耦。
前面不是说过当按钮按下,遥控器必须把电灯打开?这怎么可能解耦?
用“命令对象”可以办到!利用命令对象,把请求封装成一个特定的对象。所以如果对每个按钮都存储一个命令对象,那么当按钮被按下的时候,就可以请命令对象做相关的工作。遥控器并不需要知道工作的内容是什么,只要有个命令对象能和正确的对象沟通,把事情完成就可以了。由此实现动作的请求者与动作的执行者的解耦。
1.2 使用命令模式
首先定义命令对象的接口:Command 接口
package headfirst.designpatterns.command.simpleremote;
public interface Command {
void execute();
}
实现一个打开电灯的命令:LightOnCommand 类
package headfirst.designpatterns.command.simpleremote;
public class LightOnCommand implements Command{
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
实现一个简单的遥控器对象:SimpleRemoteControl 类,遥控器对象与电灯装置实现了解耦。
package headfirst.designpatterns.command.simpleremote;
public class SimpleRemoteControl {
Command slot;
public SimpleRemoteControl() {
}
public void setSlot(Command slot) {
this.slot = slot;
}
public void buttonWasPressed(){
slot.execute();
}
}
再实现一个 GarageDoorOpenCommand 命令对象。
package headfirst.designpatterns.command.simpleremote;
public class GarageDoorOpenCommand implements Command {
GarageDoor garageDoor;
public GarageDoorOpenCommand(GarageDoor garageDoor) {
this.garageDoor = garageDoor;
}
@Override
public void execute() {
garageDoor.up();
}
}
测试:RemoteControlTest 类
package headfirst.designpatterns.command.simpleremote;
public class RemoteControlTest {
public static void main(String[] args) {
SimpleRemoteControl remoteControl = new SimpleRemoteControl();
LightOnCommand lightOnCommand = new LightOnCommand(new Light());
remoteControl.setSlot(lightOnCommand);
remoteControl.buttonWasPressed();
GarageDoorOpenCommand garageDoorOpenCommand = new GarageDoorOpenCommand(new GarageDoor());
// 重新设置遥控器的命令对象,lightOnCommand —> garageDoorOpenCommand.
remoteControl.setSlot(garageDoorOpenCommand);
remoteControl.buttonWasPressed();
}
}
输出:
Light is on
Garage Door is Open
1.3 定义命令模式
命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持撤销的操作。
如何理解使用不同的请求来参数化其他对象呢?
这里的命令对象是LightOnCommand和GarageDoorOpenCommand,遥控器对象的插槽 Command slot,可通过这些命令对象赋值,也就是说,一个动作请求者可用不同的命令对象作参数,遥控器的插槽根本不在乎所拥有的是什么命令对象,只要该命令实现了Command接口就可以了。
命令模式的类图如下:
- Client 为测试类 Test,
- Receiver 是具体的家电装置 Light 和 GarageDoor,
- Invoker 是遥控器 SimpleRemoteControl,
- ConcreteCommand 是具体的命令对象。
命令模式通过 Command 接口和命令对象 ConcreteCommand ,实现了 Invoker 与 Receiver 的解耦。
参考
[1] Freeman E. Head First 设计模式[M] 中国电力出版社.
[2] 菜鸟教程.