情景:
屋里有很多家用电器,你需要设计一个遥控器,来控制所有电器的使用。
如果在遥控器中添加电器类,那就使得遥控器和具体电器类过度耦合了,遥控器不应该知道电器的实现细节。
遥控器应该简单一些,我们都知道遥控器只要一些按钮,所能做的动作仅仅是按下按钮,所以不应该包含太多的控制逻辑。
所以,这里需要用命令模式,来将“动作的请求者”从“动作的执行者”对象中解耦。
设计一个命令对象,遥控器可以执行命令对象,而不关心命令具体是做什么。
在命令对象内部,有具体的电器类和命令的具体实现,以及公开给遥控器的执行方法。
一个简单的实现:
首先是一个命令接口,所有的命令都要实现这个接口,然后放到遥控器上,而遥控器只要用execute()执行就好了,不需要知道命令细节。
一个具体的命令,开灯命令。
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> LightOnCommand(Light light) {
</span><span style="color: #0000ff;">this</span>.light =<span style="color: #000000;"> light;
}
@Override
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> execute() {
light.on();
}
}
灯:
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> off() {
System.out.println(</span>"灯灭了"<span style="color: #000000;">);
}
}
遥控器:遥控器上有插槽用来持有命令。(可以考虑成遥控器上一个按钮-_-#)
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> SimpleRemoteControl() {}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> setCommand(Command command) {
slot </span>=<span style="color: #000000;"> command;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> buttonWasPressed() {
slot.execute();
}
}
测试类:
remote.setCommand(lightOn);
remote.buttonWasPressed();
}
}
输出:
支持可撤销操作。
类图:
上面的遥控器只有一个命令,事实上有很多的电器需要控制,而且不仅需要开命令,也需要有关闭命令。
实现遥控器类:
有七个开启按钮和七个关闭按钮。
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> RemoteControl() {
onCommands </span>= <span style="color: #0000ff;">new</span> Command[7<span style="color: #000000;">];
offCommands </span>= <span style="color: #0000ff;">new</span> Command[7<span style="color: #000000;">];
Command noCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> NoCommand();
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < 7; i++<span style="color: #000000;">) {
onCommands[i] </span>=<span style="color: #000000;"> noCommand;
offCommands[i] </span>=<span style="color: #000000;"> noCommand;
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> setCommand(<span style="color: #0000ff;">int</span><span style="color: #000000;"> slot,Command onCommand,Command offCommand) {
onCommands[slot] </span>=<span style="color: #000000;"> onCommand;
offCommands[slot] </span>=<span style="color: #000000;"> offCommand;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> onButtonWasPushed(<span style="color: #0000ff;">int</span><span style="color: #000000;"> slot) {
onCommands[slot].execute();
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> offButtonWasPushed(<span style="color: #0000ff;">int</span><span style="color: #000000;"> slot) {
offCommands[slot].execute();
}
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> String toString() {
StringBuffer stringBuff </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StringBuffer();
stringBuff.append(</span>"\n----- Remote Control -----\n"<span style="color: #000000;">);
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < onCommands.length; i++<span style="color: #000000;">) {
stringBuff.append(</span>"[slot" + i + "] " + onCommands[i].getClass().getName() +
"\t\t" + offCommands[i].getClass().getName() + "\n"<span style="color: #000000;">);
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> stringBuff.toString();
}
}
具体命令(太多了,不贴啦):
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> StereoOffWithCDCommand(Stereo stereo) {
</span><span style="color: #0000ff;">this</span>.stereo =<span style="color: #000000;"> stereo;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> execute() {
stereo.off();
stereo.popCD();
}
}
测试一下:
Light light </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Light();
GarageDoor garageDoor </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> GarageDoor();
Stereo stereo </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Stereo();
LightOnCommand lightOnCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LightOnCommand(light);
LightOffCommand lightOffCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LightOffCommand(light);
GarageDoorOpenCommand garageDoorOpenCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> GarageDoorOpenCommand(garageDoor);
GarageDoorCloseCommand garageDoorCloseCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> GarageDoorCloseCommand(garageDoor);
StereoOnWithCDCommand stereoOnWithCDCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StereoOnWithCDCommand(stereo);
StereoOffWithCDCommand stereoOffWithCDCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StereoOffWithCDCommand(stereo);
remoteControl.setCommand(</span>0<span style="color: #000000;">,lightOnCommand,lightOffCommand);
remoteControl.setCommand(</span>1<span style="color: #000000;">,garageDoorOpenCommand,garageDoorCloseCommand);
remoteControl.setCommand(</span>2<span style="color: #000000;">,stereoOnWithCDCommand,stereoOffWithCDCommand);
System.out.println(remoteControl);
remoteControl.onButtonWasPushed(</span>0<span style="color: #000000;">);
remoteControl.offButtonWasPushed(</span>0<span style="color: #000000;">);
remoteControl.onButtonWasPushed(</span>1<span style="color: #000000;">);
remoteControl.offButtonWasPushed(</span>1<span style="color: #000000;">);
remoteControl.onButtonWasPushed(</span>2<span style="color: #000000;">);
remoteControl.offButtonWasPushed(</span>2<span style="color: #000000;">);
}
}
输出:
灯灭了
车库门开了
车库门关了
音响已打开
在音响中放入CD
音响声音调到11
音响已关闭
在音响中取出CD
重点来了,敲黑板!
有些按钮还有没有被分配命令,如果我们将其赋值为null的话,onButtonWasPressed()就要这样写:
为了减少判断的麻烦,可以为其付一个空命令,它是一个不做任何事情的对象,是一个空对象(null object)。
当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。
到目前为止,我们还有一个要求没有实现,那就是撤销功能,撤销上一条命令。
首先,修改Command接口:
然后修改遥控器类,很简单,只需要记录一下上一条命令就可以了。
<span style="color: #0000ff;">public</span><span style="color: #000000;"> RemoteControlWithUndo() {
onCommands </span>= <span style="color: #0000ff;">new</span> Command[7<span style="color: #000000;">];
offCommands </span>= <span style="color: #0000ff;">new</span> Command[7<span style="color: #000000;">];
Command noCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> NoCommand();
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < 7; i++<span style="color: #000000;">) {
onCommands[i] </span>=<span style="color: #000000;"> noCommand;
offCommands[i] </span>=<span style="color: #000000;"> noCommand;
}
undoCommand </span>=<span style="color: #000000;"> noCommand;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> setCommand(<span style="color: #0000ff;">int</span><span style="color: #000000;"> slot,Command offCommand) {
onCommands[slot] </span>=<span style="color: #000000;"> onCommand;
offCommands[slot] </span>=<span style="color: #000000;"> offCommand;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> onButtonWasPushed(<span style="color: #0000ff;">int</span><span style="color: #000000;"> slot) {
onCommands[slot].execute();
undoCommand </span>=<span style="color: #000000;"> onCommands[slot];
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> offButtonWasPushed(<span style="color: #0000ff;">int</span><span style="color: #000000;"> slot) {
offCommands[slot].execute();
undoCommand </span>=<span style="color: #000000;"> offCommands[slot];
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> undoButtonWasPushed() {
undoCommand.undo();
}
}
再次测试一下:
Light light </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Light();
GarageDoor garageDoor </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> GarageDoor();
Stereo stereo </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Stereo();
LightOnCommand lightOnCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LightOnCommand(light);
LightOffCommand lightOffCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LightOffCommand(light);
GarageDoorOpenCommand garageDoorOpenCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> GarageDoorOpenCommand(garageDoor);
GarageDoorCloseCommand garageDoorCloseCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> GarageDoorCloseCommand(garageDoor);
StereoOnWithCDCommand stereoOnWithCDCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StereoOnWithCDCommand(stereo);
StereoOffWithCDCommand stereoOffWithCDCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StereoOffWithCDCommand(stereo);
remoteControl.setCommand(</span>0<span style="color: #000000;">,stereoOffWithCDCommand);
remoteControl.onButtonWasPushed(</span>0<span style="color: #000000;">);
remoteControl.offButtonWasPushed(</span>0<span style="color: #000000;">);
remoteControl.undoButtonWasPushed();
remoteControl.onButtonWasPushed(</span>1<span style="color: #000000;">);
remoteControl.offButtonWasPushed(</span>1<span style="color: #000000;">);
remoteControl.undoButtonWasPushed();
}
}
输出:
很好,基本功能都实现了。
现在的遥控器只能撤销前一条命令,如果想要连续撤销,可以用一个栈来保存运行过的命令。
新的需求来了,现在希望按下一个按钮可以做许多事情,嗯……比如你从外面回家了,你想开灯,打开电视,打开音响……你想睡觉了,你要关灯关电视关音响。有没有办法,用一个按钮做一系列事情?
定义一个宏命令:
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> MacroCommand(Command[] commands) {
</span><span style="color: #0000ff;">this</span>.commands =<span style="color: #000000;"> commands;
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> execute() {
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < commands.length; i++<span style="color: #000000;">) {
commands[i].execute();
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> undo() {
</span><span style="color: #0000ff;">for</span> (<span style="color: #0000ff;">int</span> i = 0; i < commands.length; i++<span style="color: #000000;">) {
commands[i].undo();
}
}
}
测试:
Light light </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Light();
GarageDoor garageDoor </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> GarageDoor();
Stereo stereo </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> Stereo();
LightOnCommand lightOnCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LightOnCommand(light);
LightOffCommand lightOffCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> LightOffCommand(light);
StereoOnWithCDCommand stereoOnWithCDCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StereoOnWithCDCommand(stereo);
StereoOffWithCDCommand stereoOffWithCDCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> StereoOffWithCDCommand(stereo);
Command[] onCommands </span>=<span style="color: #000000;"> {lightOnCommand,stereoOnWithCDCommand};
Command[] offCommands </span>=<span style="color: #000000;"> {lightOffCommand,stereoOffWithCDCommand};
MacroCommand onMacroCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> MacroCommand(onCommands);
MacroCommand offMacroCommand </span>= <span style="color: #0000ff;">new</span><span style="color: #000000;"> MacroCommand(offCommands);
remoteControl.setCommand(</span>0<span style="color: #000000;">,onMacroCommand,offMacroCommand);
remoteControl.onButtonWasPushed(</span>0<span style="color: #000000;">);
remoteControl.offButtonWasPushed(</span>0<span style="color: #000000;">);
remoteControl.undoButtonWasPushed();
}
}
命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。
现在,即使在命令对象被创建许久之后,运算依然可以被调用。
事实上,它甚至可以在不同的线程中被调用。
我们可以利用这样的特性衍生一些应用
例如:“日程安排” “线程池” “工作队列” 等
工作队列和进行计算的对象之间完全是解耦的。
通过新增两个方法store()和load(),我们可以将所有的动作都记录在日志中,,并能在系统死机后,重新调用这些动作恢复到之前的状态。
Command { execute(); undo(); store(); load(); }