首页 > 代码库 > 设计模式(6)--命令模式

设计模式(6)--命令模式

关键词 :空对象 有人称为设计模式

三层调用

 

1. 封装调用  , 把封装带到一个全新的境界: 把方法调用(method invocation) 封装起来.

2. 命令模式可将"动作的请求者" 从"动作的执行者" 对象中解耦.

3. 当需要将发出的请求和执行请求的对象解耦的时候,使用命令模式.

 

OO原则: (1)封装变化 (2) 多用组合,少用继承 (3)针对接口编程,不针对实现编程 (4)为交互对象之间松耦合设计而努力

(5) 类应该对扩展开放,对修改关闭 (6) 依赖抽象,不要依赖具体类.

OO模式: 命令模式-- 将请求封装成对象,这可以让你使用不同的请求,队列,或者日志请求来参数化其它对象.命令模式也支持撤销操作.

 

调用者(遥控器)    命令   设置命令   命令接收者(电灯)    执行

 

//设计遥控器的API .  开 关  撤消

 

(1).实现命令接口

public interface Command{

     public void  execute();       

}

(2).实现一个打开电灯的命令

public class LightOnCommand implements Command{   //这是一个命令所以需要实现Command接口.

     Light light;

     public LightOnCommand(Light light){

           this.light = light;    //构造器传入某个电灯 一旦调用execute()就由电灯成为接收者负责接受请求

     }

     public void execute(){

            light.on();   //这个execute()方法调用接收对象(现在是电灯)的on()方法.

     }

}   //现在有了LightOnCommand  如何应用?

(3).有一个遥控器,只有一个按钮和一个对应的插槽,可以控制一个装置

public class SimpleRemoteControl {

    Command slot;  //有一个插槽持有命令,

    public SimpleRemoteControl(){}

    public void setCommand(Command command){

       slot = command; //这里设置插槽行为的命令

    }

    public void buttonWasPressed(){

         slot.execute();     // 调用 实现Command接口类的 execute (里面是持有对象的动作如 light.on());

    }

}

(4). 遥控器使用的简单测试

public class RemoteControlTest{   //命令模式的客户

    public static void main(String[] args){

        SimpleREmoteControl remote = new SimpleRemoteControl();  //遥控器的调用者

        Light light = new Light();

        LightOnCommand lightOn = new LightOnCommand(light);  //light 是命令的接收者

        remote.setCommand(lightOn);  //把命令传给调用者

        remote.buttonWasPressed(); //按下按钮.

    }

}

-------第二个例子

7个插槽的遥控器

public class RemoteControl {

    Command[]  onCommands;    //开的命令 

    Command[]  offCommands ;   //关的命令

    public RemoteControl(){

       onCommands = new Command[7];     //初始化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 offCommand){

          onCommands[slot]= onCommand;    //slot 位置

          offCommands[slot]= offCommand;

    }

    public void onButtonWasPushed(int slot){

        onCommands[slot] .execute();

    }

    public void offButtonWasPushed(int slot){

       offCommands[slot] .execute();

    }

}

空对象问题

有遥控器中,我们不想每次都检查是否某个插槽都加载了命令. 比方说, 在这个OnButtonWasPressed()方法中,可能需要这样的代码

public void OnButtonWasPushed(int slot){

   if(onCommands[slot] !=null){              //用了空对象 这里就可以省略.

       onCommands[slot].execute();     

   } 

}

public  class NoCommand implements Command{

   public void exectue(){}

}

//有 RemoteControl 的构造器中,为每个插槽预先指定NoCommand对象, 以便确定每个插槽永远都有命令对象

Command noCommand = new NoCommand();

for(int i =0;i<7;i++){

 onCommands[i] = noCommand;

 offCommands[i] = noCommand;

}

模式荣誉奖

NoCommand 对象是一个空对象(null object)的例子. 当你不想返回一个有意义的对象时,空对象就很有用.客户也可将处理null的责任转移给空对象. 举例来说,遥控器不可能一出厂就设置了有意义的对象,所以提供noCommand 对象作为代用品,当调用它的execute()方法时,这种对象什么事情也不做

在许多设计模式中,都会看到空对象的使用.甚至有些时个,空对象本身也被视为一种设计模式.

------撤消按钮 功能  (记住上一命令)

要加上撤消按钮的支持.加上一个新的实例变量,用来追踪最后被调用的命令. 然后调用其undo()方法.

public class RemoteControlWithUndo{

   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 offCommand){

          onCommand[slot] =onCommand;

          offCommand[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(){

      undoCommand.undo();    //按下撤消按钮时

  }

}

-------每个遥控器都需具备"Party模式"  (按下一个按钮执行多个命令) 宏命令.

public class MacroCommand implements Command{

      Command[]  commands;

      public MacroCommand(Command[]   commands){     //单个命令时这里接收的是对象接收者如(light)

           this.commands = commands;    //在宏命令中,用命令数组存储一大堆命令

      }

      public void execute(){

           for(int i =0; i<commands.length;i++){

               commands [i].execute();       //依次执行命令数组中的命令

           }

      }

     public void undo(){

        for(int i =0; i<commands.length;i++){

            commands[i]. undo   // 因为持久的命令对象如: LightOnCommand  所以可以直接撤销(调用的是其

                                                                                        LightOnComand .undo  () 方 法)

        }

     }

 

 

}

 

Command[]  partyOn = {lightOn,stereoOn, tvOn,hobutton};

MacroCommand  partyOnMacro = new MacroCommand(partyOn);

remoteControl .setCommand(0,partyOnMacro,partyOffMacro );  //将命令数组给一个按钮.

 

问: 我如何实现多层撤消操作?

答: 使用一个堆栈记录操作过程的每个命令,然后,不管什么时候按下撤销按纽,都可以从堆栈中取出最上层命令.

命令模式的更多用途:日志请求

有些应用需要将所有的命令记录在日志中,前能在系统死机后,重新调用动作恢复之前的状态. 在Java中,我们可以利用对象的序列化(Serialization)实现该方法, 但是一般认为序列化最好还是只用在对象的持久化上(persistence).

 

要点:

(1). 命令模式将发出请求的对象和执行请求的对象解耦

(2). 在被解耦的两者之间是通过命令对象沟通的,命令对象封装了接收者的一个或一组动作

(3). 调用者通过调用命令对象的execute()发出请求,这会使用接收者的动作被调用

(4). 调用者可以接受命令当参数,甚至在运行时动态的进行.

(5). 命令可以支持撤消,做法是实现一个undo()方法来回到execute()被执行前的状态.

(6). 宏命令是命令的一种简单的延伸,允许调用多个命令.宏方法也支持撤销.

(7). 实际操作时,很常见使用"聪明" 命令对象,也就是直接实现了请求而不是将工作委托给接收者

(8). 命令也可以用来实现日志和事务系统 .