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

写在前面

在上篇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() = ;
  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 > ) {
  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很容易,因为这无需改变已有的类。

写在最后

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

[学习笔记]设计模式之Command的更多相关文章

  1. [学习笔记]设计模式之Abstract Factory

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 在上篇笔记Builder设计模式中,时の魔导士祭出了自己的WorldCreator.尽管它因此能创造出一个有山有树有房子的世界,但是白 ...

  2. [学习笔记]设计模式之Builder

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 作为一个新入职的魔导士呢,哦不,是程序员,我以为并没有太多机会去设计项目的软件架构.但是,工作一段时间之后,自己渐渐意识到,哪怕是自己 ...

  3. [学习笔记]设计模式之Adapter

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 Adapter(适配器)模式主要解决接口不匹配的问题.为此,让我们要回到最初Builder模式创建平行世界时,白雪公主和小霍比特人的谜 ...

  4. [学习笔记]设计模式之Bridge

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 “魔镜啊魔镜,谁是这个世界上最美丽的人?”月光中,一个低沉的声音回荡在女王的卧室.“是美丽的白雪公主,她正和小霍比特人们幸福快乐地生活 ...

  5. [学习笔记]设计模式之Prototype

    写在前面 为方便读者,本文已添加至索引: 设计模式 学习笔记索引 在笔记Builder模式中,我们曾见到了最初用于创建平行世界的函数createWorld,并且它是Mage类的成员函数(毕竟是专属于魔 ...

  6. [学习笔记]设计模式之Chain of Responsibility

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 最近时间比较紧,所以发文的速度相对较慢了.但是看到园子里有很多朋友对设计模式感兴趣,我感觉很高兴,能够和大家一起学习这些知识. 之前的 ...

  7. [学习笔记]设计模式之Composite

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 在Composite(组合)模式中,用户可以使用多个简单的组件以形成较大的组件,而这些组件还可能进一步组合成更大的.它重要的特性是能够 ...

  8. [学习笔记]设计模式之Proxy

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 “魔镜啊魔镜,谁是这个世界上最美丽的人?” 每到晚上,女王都会问魔镜相同的问题(见Decorator模式).这是她还曾身为女巫时留下的 ...

  9. [学习笔记]设计模式之Flyweight

    为方便读者,本文已添加至索引: 设计模式 学习笔记索引 写在前面 Flyweight(享元)模式运用共享技术,可以有效地支持大量细粒度的对象.今天我们会去参观小霍比特人们的酿酒工坊……等等,不是享元模 ...

随机推荐

  1. ambari的重新安装

    ambari是什么呢? 这里我简单说一下ambari的目的,他的目的就是简化hadoop集群的安装和管理.对于安装简化到什么地步呢?只需要几个命令,在页面上配置几个参数,几百几千个节点的集群就能安装成 ...

  2. Yii Framework 开发教程Zii组件-Tabs示例

    有关Yii Tab类: http://www.yiichina.com/api/CTabView http://www.yiichina.com/api/CJuiTabs http://blog.cs ...

  3. linux diff详解

    diff是Unix系统的一个很重要的工具程序. 它用来比较两个文本文件的差异,是代码版本管理的基石之一.你在命令行下,输入: $ diff <变动前的文件> <变动后的文件> ...

  4. UILabel字体加粗等属性和特效

    /* Accessing the Text Attributes text  property font  property textColor  property textAlignment  pr ...

  5. javascript如何监听页面刷新和页面关闭事件

    本文转之http://www.qqtimezone.top 在我们的日常生活中,时常遇到这么一种情况,当我们在点击一个链接.关闭页面.表单提交时等情况,会提示我们是否确认该操作等信息. 这里就给大家讲 ...

  6. CSDN上最火的android项目

    CSDN上最火的android项目 直接拿来用!最火的Android开源项目(二) 直接拿来用最火的android开源项目(1)

  7. 微信小程序开发体验

    1.  申请小程序账号 小程序目前不支持个人申请,企业申请后填写基本信息 本来以为用原来公司申请的公众号就可以申请小程序权限,貌似不行 2.  添加开发者 管理员默认拥有开发者所有权限 添加其他开发者 ...

  8. Sublime_text3怎么运行php代码

    开发神奇sublime写代码真的好爽,之前听说是神器,但是没去用,觉得用eclipse写php代码,用dw写html够用了,用了一下sublime,哈哈,爽爆了. 除了写前端代码外,还需要写php代码 ...

  9. 读书雷达 l 业务分析师(BA)篇

    http://chuansong.me/n/412991951441 ThoughtWorks BA社区从2011年起就建立了BA书橱,根据大家的推荐选择了来自软件需求.商业分析.设计思维.软技能,以 ...

  10. C#实现FTP文件夹下载功能【转载】

    网上有很多FTP单个文件下载的方法,前段时间需要用到一个FTP文件夹下载的功能,于是找了下网上的相关资料结合MSDN实现了一段FTP文件夹下载的代码. 实现的思路主要是通过遍历获得文件夹下的所有文件, ...