背景:
我们需要设计一个多卡槽的遥控器来控制各种家具设备,注意会有很多厂商类,并且每个类还会有各式各样的方法。
设计:
关注点:遥控器应该知道如何解读按钮被按下的动作,然后发出正确的请求,但遥控器不需要知道这些家电自动化的细节,即将“动作请求者”从“动作的执行者”对象中解藕。
我们先来看餐厅的工作模式:
- 顾客生成订单并把订单交给服务员
- 服务员拿走订单放在柜台上通知厨师,不用关心订单内容和谁来准备这些
- 厨师根据订单准备餐点
服务员只需要通知厨师有订单,厨师通过订单知道要做什么,不需要和服务员沟通,故服务员和厨师之间通过订单和厨师完成了解藕。
对于遥控器API,我们同样需要分隔开“发出请求的按钮代码”和“执行请求的厂商特定对象”:
- 客户创建了命令对象并包含了一组命令(开灯,打开门,相当于顾客吃些什么)
- 客户通过setCommand()将命令对象存储在调用者中(相当于交给服务员)
- 调用者通过excute()方法让Receiver对象执行(相当于服务员通知厨师)
- Receiver对象开始执行
实现:
// 实现命令接口
public interface Command {
public void execute(); // 所有的命令对象都要有这个方法
}
// 开灯命令
public class LightOnCommand implements Command {
Light light;
// 构造器传入某个电灯,以便命令控制,一旦执行execute(),就由这个电灯对象成为接收者,负责接受请求
public LightOnCommand(Light light){
this.light = light;
}
public void execute(){
light.on()
}
}
// 调用者 简化:一个遥控器只有一个按钮和对应的卡槽,可以控制一个装置
public class SimpleRemoteControl {
Command slot; // 一个卡槽持有命令,该命令控制者一个装置
// 设置卡槽控制的命令 调用者不知道是什么命令
public void setCommand(Command command) {
slot = command;
}
// 按下按钮就执行了
public void buttonWasPressed() {
slot.execute();
}
}
// 客户
public class RemoteControlTest {
public static void main(string[] args) {
SimpleRemoteControl remote = new SimpleRemoteControl(); //遥控器就是调用者
Light light = new Light(); // 请求的接收者
LightOnCommand lightOn = new LightOnCommand(light); // 创建命令,将接收者传给它
remote.setCommand(lightOn); // 把命令传给调用者
remote.buttonWasPressed(); // 按下按钮
}
}
总结:
命令模式:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了哪些动作,只知道如果调用了execute()方法,请求的目的就能达到。
因为使用了命令模式,所以我们还可以实现更多功能和用途:
- 撤销功能:只需在命令中加入undo()方法,在每次执行前记录一下当前的状态
- 批处理功能:设计一个宏命令并包含多组命令。
- 队列请求:命令可以将运算块打包(接收者和一组动作),这样就可以传来传去。每个线程都可以从线程中取出一个命令然后执行。
- 日志请求:将历史记录存储在磁盘上,一旦系统司机,我们可以将命令对象重新家在,成批的调用这些对象的execute()方法。