目录
命令模式
我们将封装带入到一个全新的境界:把方法调用(Method Invocation)封装起来。
命令模式可以将“动作的请求者”从“动作的执行者”对象中解耦。
利用命令对象,把请求(例如打开电灯)封装成一个特定对象(例如客厅电灯对象)。所以,如果对每个按钮都存储一个命令对象,那么当按钮被按下时,就可以请命令对象做相关的工作。遥控器并不需要知道工作内容是什么,只要有个命令对象能和正常的对象沟通,把事情做好就可以了。这样就实现了遥控器和电灯的解耦。
这个模型允许将“发出请求的对象”和“接受与执行这些请求的对象”分开。
public interface Command {
public void execute();
}
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
}
public class SimpleRemoteControl {
Command slot;
public SimpleRemoteControl() {}
public void setCommand(Command command) {
slot = command;
}
//当按下按钮时,这个方法就会被调用,使得当前命令衔接插槽,并调用它的execute方法。
public void buttonWasPressed() {
slot.execute();
}
}
上述的设计中利用了将接口作为组合成员变量,同时通过类implements接口来实现多态性。命令对象持有接收者实例的引用。即LightOnCommand对象中持有Light对象的引用。
命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
一个命令对象通过在特定接收者上绑定一组动作来封装一个请求(将接收者的动作封装在execute函数中)。要达到这一点,命令对象将动作和接收者包含金对象中。这个对象只暴露出一个execute方法,当此方法被调用时,接收者就会执行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了哪些动作,只知道如果调用execute()方法,请求的目的就达到了。
遥控器插槽根本不在乎所拥有的的什么命令对象,只要将该命令对象实现了Command接口就可以了(这里充分体现了接口的灵活性)。
NoCommand对象是一个空对象(null object)的例子。当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。当NoCommand对象作为替代品时,调用它的execute()方法,这种对象什么事情也不做。
- 接收者一定有必要存在吗?为什么命令对象不直接实现execute()方法的全部细节?
我们尽量设计“傻瓜”命令对象,它只懂得调用一个接收者的一个行为。当然也可以实现“聪明的”命令对象,但这样调用者和接收者之间的解耦程度比不上“傻瓜”命令对象。
命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。命令模式的命令对象在创建很久后,运算也可以被调用。它也可以在不同的线程中调用。
命令模式的一些衍生应用,如日程安排(Scheduler)、线程池、工作队列等。例如,工作队列对象不在乎到底做些什么,它们只知道取出命令对象,然后调用其execute()方法。
命令模式可以用于日志请求。通过新增两个方法(store()和load()),命令模式就能够支持这一点。在Java中,我们可以利用对象的序列化(Serialization)实现这些方法,但是一般认为序列化UI好还是只用在对象的持久化(persistence)上。
在实现日志请求时,我们执行命令,并将历史记录存储在磁盘上。一旦系统死机,我们就可以将命令对象重新加载,并成批地依次调用这些对象的execute()方法。
命令模式总结
- 命令模式将发出请求的对象和执行请求的对象解耦。
- 在被解耦的两者之间是通过命令对象进行沟通的。命令对象封装了接收者和一组动作。
- 调用者通过调用命令对象的execute()方法发出请求,这会使得接收者的动作被调用。
- 调用者可以接收命令当做参数,甚至在运行时动态地进行。
- 命令可以支持撤销,做法是实现一个undo()方法来回到execute()被执行之前的状态。
- 宏命令是命令的一种简单延伸,允许调用多个命令。宏方法也支持撤销。
- 实际操作时,很常见使用“聪明”命令对象,也就是直接实现了请求,而不是将工作委托给接收者。
- 命令也可以用来实现日志和事务系统。