天天看点

COMMAND(命令) ---- 对象行为型模式

[size=large]1、意图

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录日志,以及支持可撤销的操作。

2、别名

动作(Action),事务(Transaction)

3.动机

有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。例如,用户界面工具箱包括按钮和菜单这样的对象,它们执行请求响应用户的输入。但工具箱不能显示的在按钮或菜单中实现请求,因为只有使用工具箱的应用知道该由哪个对象做哪个操作。而工具箱的设计者无法知道请求的接受者或执行的操作。

命令模式通过将请求本身变成一个对象来使工具箱对象可向未指定的应用对象提出请求。这个对象可被存储并向其他的对象一样被传递。这一模式的关键是一个抽象的Command类,它定义了一个执行操作的接口。其最简单的形式是一个抽象的Execute操作。具体的Command子类将接受者作为其一个实例变量,并实现Execute操作,指定接受者采取的动作。而接受者有执行该请求所需的具体信息。

[img]http://dl.iteye.com/upload/attachment/0081/2072/f8c65ecd-a478-3542-ba82-5a9913ca6210.jpg[/img]

用Command对象可以很容易的实现菜单(Menu),每一个菜单中的选项都是一个菜单项(MenuItem)的实例。一个Applacation类创建这些菜单和它们的菜单项以及其余的用户界面。该Application类还跟踪用户已经打开的Document对象。

该应用为每一个菜单项配置一个具体的Command子类的实例。当用户选择了一个菜单项时,该MenuItem对象调用它的Command对象的Execute方法,而Execute执行相应的操作。MenuItem对象并不知道他们使用的是Command的哪一个子类。Command子类存放着请求的接受者,而Execute操作将调用该接受者的一个或多个操作。

例如,PasteCommand支持从剪贴板向一个文档(Document)粘贴正文。PasteCommand的接受者是一个文档对象,该对象是实例化时提供的。Execute操作将调用该Document的Paste操作。

[img]http://dl.iteye.com/upload/attachment/0081/2086/f9c8ddb5-5bd6-3179-854b-22c7b5d3e8aa.jpg[/img]

而OpenCommand的Execute操作却有所不同:它提示用户输入一个文档名,创建一个相应的文档对象,将其加入作为接受者的应用对象中,并打开该文档。

[img]http://dl.iteye.com/upload/attachment/0081/2101/5f0891cc-e717-37e6-8076-d8f63ef23d2d.jpg[/img]

有时一个MenuItem需要执行一系列命令。例如,使一个页面按正常大小居中的MenuItem可由一个CenterDocumentCommand对象和一个NormalSizeCommand对象构建。因为这种需将多条命令串联起来的情况很常见,我们定义一个MacroCommand类来让一个MenuItem自行任意数目的命令。MacroCommand是一个具体的Command子类,它接受一个命令序列。MacroCommand没有明确的接受者,而序列中的命令各自定义其接受者。

[img]http://dl.iteye.com/upload/attachment/0081/2109/61337ba0-cc60-3bf5-9d67-18633cdbabda.jpg[/img]

请注意这些例子中Command模式是怎样解耦调用的对象和具有执行该操作所需信息的那个对象的。这样我们在设计用户界面时拥有很大的灵活性。一个应用如果想让一个菜单或一个按钮代表同一个功能,只需让他们共享相应Command子类的同一个实例即可。我们还可以动态地替换Command对象,这可用于实现上下文有关的菜单。我们也可通过将几个命令组合形成更强大的命令的形式来支持命令脚本(command scripting)。所有这些之所以成为可能乃是因为提交一个请求的对象仅需知道如何提交它,而不需知道该请求将会被如何执行。

4、适用性

当有以下需求时,可使用Command模式:

1)像上面提到的MenuItem对象那样,抽象出待执行的动作以参数化某对象。你可用过程语言中的回调(callback)函数表达这种参数化机制。所谓回调函数是指函数现在某处注册,而它在需要的时候被调用。Command模式是回调机制的一个面向对象的替代品。

2)在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接受者可用一种与地址空间无关的方式表达,那么久可将负责该请求的命令对象传给另一个不同的进程并在那儿实现该请求。

3)支持取消操作。Command的Execute操作可在实施操作前将状态存储起来,在取消该操作时这个状态用来取消该操作的影响。Command接口必须添加一个UnExecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表分别调用UnExecute和Execute来实现重数不多的“取消”和“重做”。

4)支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从奔溃中回复的过程包括从磁盘重新读入记录下来的命令并用Execute操作重新执行它们。

5)用构建在原语操作上的高层构造一个系统。这样一种结构在支持事务的信息系统中很常见,一个事务分装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同事使用该模式也易于添加新事务以扩展系统。

5、结构

[img]http://dl.iteye.com/upload/attachment/0081/2120/5a6ed9e3-4965-305d-8e9f-7f1477377f65.jpg[/img]

参与者

Command

----声明执行该操作的接口

ConcreteCommand(PasteCommand,OpenCommand)

----将一个接收者对象绑定与一个动作。

----调用接收者相应的操作,以实现Execute。

Client(Application)

----创建一个具体命令对象并设定它的接收者。

Invoke(MenuItem)

----要求该命令执行这个请求

Receiver(Document,Application)

----知道如何实施与执行一个请求相关的操作。任何类都可能成为一个接收者。

6、示例[/size]

/* The Command interface */
public interface Command {
   void execute();
}

import java.util.List;
import java.util.ArrayList;

/* The Invoker class */
public class Switch {
   private List<Command> history = new ArrayList<Command>();

   public Switch() {
   }

   public void storeAndExecute(Command cmd) {
      this.history.add(cmd); // optional 
      cmd.execute();        
   }
}

/* The Receiver class */
public class Light {
   public Light() {
   }

   public void turnOn() {
      System.out.println("The light is on");
   }

   public void turnOff() {
      System.out.println("The light is off");
   }
}

/* The Command for turning on the light - ConcreteCommand #1 */
public class FlipUpCommand implements Command {
   private Light theLight;

   public FlipUpCommand(Light light) {
      this.theLight = light;
   }

   public void execute(){
      theLight.turnOn();
   }
}

/* The Command for turning off the light - ConcreteCommand #2 */
public class FlipDownCommand implements Command {
   private Light theLight;

   public FlipDownCommand(Light light) {
      this.theLight = light;
   }

   public void execute() {
      theLight.turnOff();
   }
}

/* The test class or client */
public class PressSwitch {
   public static void main(String[] args){
      Light lamp = new Light();
      Command switchUp = new FlipUpCommand(lamp);
      Command switchDown = new FlipDownCommand(lamp);

      Switch s = new Switch();

      try {
         if (args[0].equalsIgnoreCase("ON")) {
            s.storeAndExecute(switchUp);
         }
         else if (args[0].equalsIgnoreCase("OFF")) {
            s.storeAndExecute(switchDown);
         }
         else {
            System.out.println("Argument \"ON\" or \"OFF\" is required.");
         }
      } catch (Exception e) {
         System.out.println("Arguments required.");
      }
   }
}