命令模式,从字面义来看就好像是上级对下级下达命令,上级只管通知下级,但是不会知道下级是如何完成的。假设一个场景:某个公司,想知道实现全自动家庭,其有很多合作商,好比电视机厂商,油烟机厂商等,他们的产品的代码的接口都是不同的,然而你作为一个CTO,你的CEO期望你能够将这些东西完整的组织起来,真正的完成这项工作,开拓出新的市场。
在这里正好有一种模式可以提供一种新的思路或者是方法----命令模式,如果觉得上述的文字太过于简洁,我们可以参考下面的例子:
这个例子模拟了对灯的控制,灯这个接口仅仅定义了on和off两个方法来控制灯的状态,所以我们基于讨论的命令模式来写一个对灯的控制的例子:
首先定义一个Command类的接口,里面定义了需要执行的方法:
public interface Command{
public void execute();
}
其次,我们需要构建一个LightOnCommand类继承这个接口,并实现了一个灯打开的类:
public class LightOnCommand implements Command{
Light light;
public LightOnCommand(Light light){
this.light = light;
}
public void execute(){
light.on();
}
}
可以看到,light对象的on方法被封装到execute方法里面,execute方法则有LIghtOnCommand方法调用;
之后构建一个SimpleRemoteControl类来调用执行命令,即执行execute,通过setCommand方法将LightOnCommand对象传入,通过buttonWasPressed来模拟按钮按下的过程,这样就可以比较完整的模拟了电灯被按钮控制的过程。
下面开始进行测试:
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();
}
}
测试结果如下:
假设我们按照上面的方式,将其它的灯也这样组织起来,我们暂定为有7个控制按钮,接下来实现这个远程操纵的遥控器:
public class RemoteControl{
Command[] onCommands;
Command[] offCommands;
public RemoteControl(){
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;
}
}
public void setCommand(int slot, Command onCommand,Command offCommad){
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot){
onCommands[slot].execute();
}
public void offButtonWasPushed(int slot){
offCommands[slot].execute();
}
public String toString(){
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n-------Remote Control------\n");
for (int i = 0; i < onCommands.length ;i++ ){
stringBuff.append("[slot " + +"] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass.getName() + "\n");
}
}
return stringBuff.toString();
}
接着,对于命令的构造,延续上面的Command接口,以及LightOnCommand类,下面实现命令LightOffCommand类:
public class LightOffCommand implements Command{
Light light;
public LightOffCommand(Light light){
this.light = light;
}
public void execute(){
light.off();
}
}
到这里我们再添加一个音响类吧,这样的话,可以模拟关灯,听音乐的场景,有点无厘头哈,为了能够说明白这个模式,那就将就着吧,哈哈,或者添加到了其它的场所,下面设计这个类:
public class StereoOnWithCDCommand implements Command{
Stereo stereo;
public StereoOnWithCDCommand(Stereo steroe){
this.stereo = stereo;
}
public void execute(){
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
让我们来测试这些命令吧:
public class RemoteLoader{
public static void mian(String[] args){
RemoteCOntrol remoteControl = new RemoteControl();
//将所有的装置创建在适合的位置
Light livingRoomLight = new Light("living Room");
Light kitchenLight = new Light("Kitchen");
CellingFan ceilingFan = new CeilingFan("Living Room");
GarageDoor garageDoor = new GarageDoor("");
Stereo stereo = new Stereo("living Room");
//创建所有的电灯命令对象
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
LightOnCommand ketchenLightOn = new LightOnCommand(kitchenLight);
LightOffCommand ketchenLightOff = new LightOffCommand(kitchenLight);
//创建吊扇的开关命令
CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
GarageDoorUpCommand garageDoorUp = new GarageDoorUpCommand(garageDoor);
GarageDoorDownCommand garageDoorDown = new GarageDoorDownCommand(garageDoor);
StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo);
StereoOffCommand stereoOff = new StereoOffCommand(stereo);
remoteControl.setCommand(0,livingRoomLightOn,livingRoomLightOff);
remoteControl.setCommand(1,kitchenLightOn,kitchenLightOff);
remoteControl.setCommand(2,ceilingFanOn,ceilingFanOff);
remoteControl.setCommand(3,stereoOnWithCD,stereoOnWithCD,stereoOff);
System.out.println(remoteControl);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
remoteControl.onButtonWasPushed(3);
remoteControl.offButtonWasPushed(3);
}
}
在上面的代码中你可能会发现里面有一个NoCommand类,是的,这个是用作再RemoteControl构造器中,将每个插槽都预先置顶成为NoCommand对象,以确定每个插槽永远都有命令对象。所以在测试中,没有明确指出的指令命令的插槽都是默认的NoCommand对象。上述的过程能够帮助我们实现正向的打开电灯等的过程,那么我们如何设计一个逆向的撤销方法呢?
我们在上面的代码中定义undo方法,也就是说我们在原来的Command接口中加入undo方法的定义:
public interface Command{
public void execute();
public void undo();
}
接着,利用电灯的例子实现undo方法:
public class LightOnCommand implements Command{
Light light;
public LightOnCommand(Light light){
this.light = light;
}
public void execute(){
light.on();
}
public void undo(){
light.off();
}
}
然后在LightOffCommand类中实现undo方法,其实就是调用on()方法:
public class LightOffCommand implements Command{
Light light;
public LightOffCommand(Light light){
this.light = light;
}
public void execute(){
light.off();
}
public void undo(){
light.on();
}
}
仅仅在原有类中实现了好像还是不可以,因为不知道怎么去追踪控制器按下的是哪一个按钮,在这里新设计了一个类,RemoteControlWithUndo类:
public class ReomteControlWithUndo{
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
public RemoteControlWithUndo(){
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 offCommad){
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot){
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot){
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
public void undoButtonWasPushed(int slot){
undoCommand.undo();
}
public String toString(){
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n-------Remote Control------\n");
for (int i = 0; i < onCommands.length ;i++ ){
stringBuff.append("[slot " + +"] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass.getName() + "\n");
}
}
为了测试这个功能,我们在刚才设计的RemoteLoader类的主函数中添加下面的内容(仅列出了需要增加或者是修改的内容):
public class RemoteLoader{
public static void mian(String[] args){
RemoteCOntrolWithUndo remoteControl = new RemoteControlWithUndo();
//将所有的装置创建在适合的位置
Light livingRoomLight = new Light("living Room");
//创建所有的电灯命令对象
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
remoteControl.setCommand(0,livingRoomLightOn,livingRoomLightOff);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
System.out.println(remoteControl);
remoteControl.undoButtonWasPushed();
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(0);
System.out.println(remoteControl);
remoteControl.undoButtonWasPushed();
}
}
说到这里,要说两个小问题,第一,我们能不能实现多层次的撤销操作,换句话说能不能撤销到较早的状态。可以的,不过要记录使用过的命令,而不是和上面一样仅仅记录一个命令了,将这些命令压倒栈中,当执行撤销命令时,可以从栈中找到上面的命令,然后调用它的undo方法。
第二个问题是,如何能够更好的通过按下一个按钮同时处理灯光等其它物件的控制呢,我们可以通过构建一个综合的命令,宏命令,也就是说将所有的包含on的命令集合起来统一管理,将所有的off命令统一起来,之后,放置到固定的数组或者其他容器中,之后开始调用。命令模式就先介绍到这里,之后在进行补充。