zoukankan      html  css  js  c++  java
  • [学习笔记]设计模式之Command

    为方便读者,本文已添加至索引:

    写在前面

    在上篇Chain of Responsibility(职责链)模式笔记中,我们学习了一种行为型设计模式。今天,我们继续这一主题,来学习下Command(命令)模式。可以看到职责链模式是对处理请求的对象(职能者)进行了建模,而Command模式的最大不同之处就在于,它是对请求本身进行建模的。这一点从它的名字就可以看出。所以它又有别名叫:Action(动作)、Transaction(事物)模式。

    老规矩,我们首先直观地去理解什么是命令模式。日常工作中,我们常常会接受到一些“任务”,这些任务往往伴随着具体要求,并对应着特定的执行人。比如说我们做一个项目,我负责其中一个Feature。那么之后,上头提出的关于这个Feature的任何修改的“command”,都会让我去执行,因为我拥有开发这个Feature所必需的相关信息。

    实现这一模型的关键点就在于Command的抽象类,它定义了一个执行操作的接口,比如说execute()操作。具体的一个Command子类将接收者作为它的一个实例变量保存,并实现execute()操作,指定接收者行动起来。我们将在示例部分进行更深入地实例讲解,那么在此之前我们可以了解下它的基本要点:

    要点梳理

    • 目的分类
      • 对象行为型模式
    • 范围准则
      • 对象(该模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性)
    • 主要功能
      • 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作
    • 适用情况
      • 当我们需要抽象出待执行的动作以参数化某对象时。
      • 在不同的时刻指定、排列和执行请求。
      • 需要支持取消操作。
      • 需要支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。
      • 用构建在原语操作上的高层操作构造一个系统。
    • 参与部分
      • Command:声明执行操作的接口
      • ConcreteCommand:将一个接收者对象绑定于一个动作;调用接收者相应的操作,以实现execute
      • Client:创建一个具体命令对象并设定它的接收者
      • Invoke:要求该命令执行这个请求
      • Receiver:知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者
    • 协作过程
      • Client创建一个ConcreteCommand对象并指定它的Reciever对象
      • 某个Invoker对象存储ConcreteCommand对象
      • Invoker通过调用Command对象的execute操作来提交一个请求。若该命令是可以取消的,那么ConcreteCommand就在执行execute之前存储当前状态以用于撤销
      • ConcreteCommand对象对调用它的Receiver的一些操作执行该请求
    • UML图

    示例分析 - 魔法小人偶的驱动器

    让我们回顾下女巫格琳达的私人订制魔法小人偶吧(详见Composite模式笔记)。它通过某种神秘的传感器作为Input装置,接收人们下达的命令,然后执行。为了简单起见,我们今天举一个具体的“武斗人偶”的例子。这种人偶俨然一位空手格斗大师,为了灵活地展现武斗技巧,它分别有对应于手臂和腿部的驱动装置(ArmDrive & LegDrive),人们可以给它下达诸如“挥击”(Punch)、“飞踢”(FlyingKick) 等命令。为了增强人偶功能的扩展性,我们当然希望它日后可以执行更多更多的命令。Command设计模式的引入帮我们很好地解决了需求。它使得调用操作的对象与知道如何实现该操作的对象解耦。我们的例子中,人偶的传感器就是调用操作的对象,而各种驱动器则是实现具体操作的对象,Command模式使得传感器不需要知道任何驱动器的信息,这非常有助于我们日后开发更多的驱动装置。

    首先来看最重要的Command类:

     1 // ... Direction definitions ...
     2 #define NONE    0
     3 #define D_SOUTH 1
     4 #define D_NORTH 2
     5 #define D_WEST  3
     6 #define D_EAST  4
     7 
     8 // ... Abstract Command class ...
     9 class Command {
    10 public:
    11     virtual ~Command();
    12     virtual void execute() = 0;
    13 protected:
    14     Command();
    15 };

    接下来是PunchCommand,它会令ArmDrive对象朝用户指定的方向来狠狠重击。注意,PunchCommand的构造器需要一个ArmDrive对象作为参数。askDirection是提示用户告诉它具体的方向。

     1 // Punch !
     2 class PunchCommand : public Command {
     3 public:
     4     PunchCommand(ArmDrive*);
     5     
     6     virtual void execute();
     7 protected:
     8     virtual int askDirection();
     9 private:
    10     ArmDrive* _drive;
    11 };
    12 
    13 PunchCommand::PunchCommand(ArmDrive* a) {
    14     _drive = a;
    15 }
    16 
    17 void PunchCommand::execute() {
    18     int direction = askDirection();
    19     
    20     if (direction) {
    21         _drive->punch(direction);
    22     }
    23 }

    同理我们有KickCommand需要一个LegDrive对象作为其接收者。其中askHeight是询问用户需要踢击的高度,必要的时候它得跳起来。

     1 // Kick !
     2 class KickCommand : public Command {
     3 public:
     4     KickCommand(LegDrive*);
     5     
     6     virtual void execute();
     7 protected:
     8     virtual int askHeight();
     9     virtual int askDirection();
    10 private:
    11     LegDrive* _drive;      
    12 };
    13 
    14 KickCommand::PunchCommand(LegDrive* l) {
    15     _drive = l;
    16 }
    17 
    18 void KickCommand::execute() {
    19     int direction = askDirection();
    20     int height = askHeight();
    21     
    22     if (direction && height > 0) {
    23         _drive->kick(direction, height);
    24     }
    25 }

    这样一来,我们的传感器就可以通过接收并调用命令来使得小人偶动起来了:

    1 // ... Sensor Process ...
    2 ArmDrive* arm = ArmDrive::getInstance();
    3 LegDrive* leg = LegDrive::getInstance();
    4 Command* aCommand = new PunchCommand(arm);
    5 aCommand->execute();    // execute command.
    6 delete aCommand;
    7 aCommand = new KickCommand(leg);
    8 aCommand->execute();    // execute command.

    Command模式的好处还不仅限于此。大家一定熟悉批处理、宏等概念吧。结合Composite设计模式(请见索引),我们可以设计MacroCommand,从而批量执行一系列的命令。而对于MarcroCommand本身来说,它也无需要知道其各个子命令的具体执行者,因为它只需调用子命令即可。它具有add和remove方法来管理子命令。

     1 // ... Macro Command ...
     2 class MacroCommand : public Command {
     3 public:
     4     MacroCommand();
     5     virtual ~MacroCommand();
     6     
     7     virtual void add(Command*);   
     8     virtual void remove(Command*);
     9     
    10     virtual void execute();
    11 private:
    12     List<Command*>* _cmds;
    13 };
    14 
    15 void MacroCommand::execute() {
    16     ListIterator<Command*> i (_cmds);
    17     
    18     for (i.first(); !i.isDone(); i.next()) {
    19         Command* c = i.currentItem();
    20         c->execute();
    21     }
    22 }
    23 
    24 void MacroCommand::add (Command* c) {
    25     _cmds->append(c);
    26 }
    27 
    28 void MacroCommand::remove(Command* c) {
    29     _cmds->remove(c);
    30 }

    举例来看,我们想让小人偶打出一系列拳脚组合的“连招”:

    1 // ... Punch Punch  Kick and Punch ...
    2 MacroCommand* aMCmd = new MacroCommand();
    3 aMCmd->add(new PunchCommand(arm));
    4 aMCmd->add(new PunchCommand(arm));
    5 aMCmd->add(new KickCommand(leg));
    6 aMCmd->add(new PunchCommand(arm));
    7 aMCmd->execute();

    怎么样,是不是很酷!让我们来看一下这个设计的UML图:

    特点总结

    从以上我们可以看到,Command模式具有如下一些特点:

    1. 它将调用操作的对象与知道如何实现该操作的对象解耦;
    2. Command是头等的对象。它们可像其他的对象一样被操纵和扩展;
    3. 可将多个命令装配成一个复合命令。比如MacroCommand;
    4. 增加新的Command很容易,因为这无需改变已有的类。

    写在最后

    今天的笔记就到这里了,欢迎大家批评指正!如果觉得可以的话,好文推荐一下,我会非常感谢的!

  • 相关阅读:
    Java中的多线程你只要看这一篇就够了
    用Spring Boot颠覆Java应用开发
    Java Web 学习路线
    Java基本概念(2)J2EE里面的2是什么意思
    Java基本概念(1)什么是Java
    《Effective Java》读书笔记一(创建与销毁对象)
    Java:集合,对列表(List)中的数据(整型、字符串、日期等)进行排序(正序、倒序)的方法;字符串按照整型排序的方法
    Java:几个正则式应用(检查汉字、日期、EMAIL、手机号码的合法性,替换字符串等)
    Solr4:查询参数fq的用法(对结果进行过滤;两组关键词组合查询)
    Oracle删除重复记录只保留一条数据的几种方法
  • 原文地址:https://www.cnblogs.com/xieziyu/p/3625123.html
Copyright © 2011-2022 走看看