一、定义
The Command Pattern encapsulates a request as an
object, thereby letting you parameterize other objects
with different requests, queue or log requests, and support
undoable operations.
命令模式把请求封装为一个对象,因此,你可以使用不同的请求、队列或日志请求
参数化其他对象,并支持可撤消的操作。
从定义中,可以得知命令模式的核心在于把请求封装为对象,它的应用主要在处理请求队列、日志请求、事务处理等方面。
二、实例
一间房子有三种电器可以通过遥控来控制:电灯、音响、车库门。如何实现远程遥控控制?同时支持撤销操作?
(一)直观思路:三个实体类电灯、音响、车库门,和一个控制类,控制类有三个类的对象引用。这种思路太古板了,勉强能实现功能,但是它的可扩展性很低,如果再增加一个电器或删除一个电器,代码就得大幅度删改,不符合开闭原则。也不能满足撤销功能。
class Light {
public void on() {
System.out.println("开灯");
}
public void off() {
System.out.println("关灯");
}
}
class Stereo {
public void on() {
System.out.println("开音响");
}
public void off() {
System.out.println("关音响");
}
}
class GarageDoor {
public void on() {
System.out.println("开车库门");
}
public void off() {
System.out.println("关车库门");
}
}
public class RemoteControl {
private Light light;
private Stereo stereo;
private GarageDoor door;
public RemoteControl(Light light, Stereo stereo, GarageDoor door) {
this.light = light;
this.stereo = stereo;
this.door = door;
}
public void lightOn() {
light.on();
}
public void lightOff() {
light.off();
}
public void stereoOn() {
stereo.on();
}
public void stereoOff() {
stereo.off();
}
public void doorOn() {
door.on();
}
public void doorOff() {
door.off();
}
public static void main(String[] args) {
RemoteControl rc=new RemoteControl(new Light(), new Stereo(), new GarageDoor());
rc.lightOn();
rc.stereoOn();
rc.doorOn();
rc.lightOff();
rc.stereoOff();
rc.doorOff();
}
}
(二)根据多态的性质,我们可以把每种设备的行为封装起来,做成一个接口。同时,为了降低对设备的依赖性,考虑到设备的打开与关闭是两个操作,为每种设备创建一个打开操作类和关闭操作类,都实现公有接口,同时持有对应设备的引用,控制类只需要拥有公有接口类型的引用即可。
public interface Command {
public void execute();
public void undo();
public static void main(String[] args) {
SimpleRemoteControl remote=new SimpleRemoteControl();
Light light=new Light();
Stereo stereo=new Stereo();
GarageDoor garageDoor=new GarageDoor();
LightOnCommand lightOn=new LightOnCommand(light);
LightOffCommand lightOff=new LightOffCommand(light);
StereoOnCommand stereoOn=new StereoOnCommand(stereo);
StereoOffCommand stereoOff=new StereoOffCommand(stereo);
GarageDoorOpenCommand garageOpen=new GarageDoorOpenCommand(garageDoor);
GarageDoorCloseCommand garageClose=new GarageDoorCloseCommand(garageDoor);
remote.setCommand(0, lightOn, lightOff);
remote.setCommand(1, stereoOn, stereoOff);
remote.setCommand(2, garageOpen, garageClose);
System.out.println(remote);
remote.onButtonPressed(0);
remote.onButtonPressed(1);
remote.onButtonPressed(2);
remote.offButtonPressed(0);
remote.undo();
remote.offButtonPressed(1);
remote.offButtonPressed(2);
remote.undo();
}
}
//什么都不做
class NoCommand implements Command {
@Override
public void execute() { }
@Override
public void undo() {}
}
class Light {
public void on() {
System.out.println("灯->开灯");
}
public void off() {
System.out.println("灯->关灯");
}
}
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
class Stereo {
public void on() {
System.out.println("立体声音响->打开");
}
public void off() {
System.out.println("立体声音响->关闭");
}
}
class StereoOnCommand implements Command {
private Stereo stereo;
public StereoOnCommand(Stereo stereo) {
this.stereo=stereo;
}
@Override
public void execute() {
stereo.on();
}
@Override
public void undo() {
stereo.off();
}
}
class StereoOffCommand implements Command {
private Stereo stereo;
public StereoOffCommand(Stereo stereo) {
this.stereo=stereo;
}
@Override
public void execute() {
stereo.off();
}
@Override
public void undo() {
stereo.on();
}
}
class GarageDoor {
public void open() {
System.out.println("门->开门");
}
public void close() {
System.out.println("门->关门");
}
}
class GarageDoorOpenCommand implements Command {
private GarageDoor gd;
public GarageDoorOpenCommand(GarageDoor gd) {
this.gd = gd;
}
@Override
public void execute() {
gd.open();
}
@Override
public void undo() {
gd.close();
}
}
class GarageDoorCloseCommand implements Command {
private GarageDoor gd;
public GarageDoorCloseCommand(GarageDoor gd) {
this.gd = gd;
}
@Override
public void execute() {
gd.close();
}
@Override
public void undo() {
gd.open();
}
}
class SimpleRemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Command undoCommand;
public SimpleRemoteControl() {
onCommands=new Command[7];
offCommands=new Command[7];
Command noCommand=new NoCommand();
for(int i=0;i<7;i++) {
onCommands[i]=noCommand;
offCommands[i]=noCommand;
}
undoCommand=noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot]=onCommand;
offCommands[slot]=offCommand;
}
public void onButtonPressed(int slot) {
onCommands[slot].execute();
undoCommand=onCommands[slot];
}
public void offButtonPressed(int slot) {
offCommands[slot].execute();
undoCommand=offCommands[slot];
}
public void undo() {
undoCommand.undo();
}
public String toString() {
StringBuffer string=new StringBuffer();
string.append("\n------ Remote Control ------\n");
for(int i=0;i<onCommands.length;i++) {
string.append("[slot "+i+"] "+onCommands[i].getClass().getName()+" "
+offCommands[i].getClass().getName()+"\n");
}
return string.toString();
}
}
运行结果:
------ Remote Control ------
[slot 0] chapter6.command.demo2.LightOnCommand chapter6.command.demo2.LightOffCommand
[slot 1] chapter6.command.demo2.StereoOnCommand chapter6.command.demo2.StereoOffCommand
[slot 2] chapter6.command.demo2.GarageDoorOpenCommand chapter6.command.demo2.GarageDoorCloseCommand
[slot 3] chapter6.command.demo2.NoCommand chapter6.command.demo2.NoCommand
[slot 4] chapter6.command.demo2.NoCommand chapter6.command.demo2.NoCommand
[slot 5] chapter6.command.demo2.NoCommand chapter6.command.demo2.NoCommand
[slot 6] chapter6.command.demo2.NoCommand chapter6.command.demo2.NoCommand
灯->开灯
立体声音响->打开
门->开门
灯->关灯
灯->开灯
立体声音响->关闭
门->关门
门->开门
分析:
(1)NoCommand类虽然什么都不做,但却是有用的,在控制类SimpleRemoteControl的构造函数中,为打开类和关闭类都初始化为NoCommand,这是为了使得后续的操作不必再判断是否为null。
(2)可以使得程序对实体类(Light、Stereo、GarageDoor)的耦合度降低,所有的命令都实现Command接口,利用多态的特性,在控制类SimpleRemoteControl写出高可用性,在setCommand中可以设置任意设备类型的开和关。
(3)注意到,构造函数中只定义了7个开和关,优化后可以使用两个List,分别记录开和关命令,之后的setCommand就改成addCommand(Command on, Command off)。最后的onButtonPressed和offButtonPressed可以判断下标是否越界。
(4)实际上,每个具体的Command都可以把引用变量修改为引用变量数组,即控制多台相同的设备。execute和undo方法中都用循环来操作。
类图如下:
三、总结
命令模式的核心是把请求封装成对象,如把一个请求封装到线程中,通常用来处理请求队列、日志操作、事务操作,并且还支持撤销操作(相当于ctrl+z)。