设计模式(18)-Command Pattern

分类: 设计模式 2006-11-10 20:52 169人阅读 评论(0) 收藏 举报

一、 命令(Command)模式

命令(Command)模式属于对象的行为模式【GOF95】。命令模式又称为行动(Action)模式或交易(Transaction)模式。命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。

命令模式是对命令的封装。命令模式把发出命令的责任和执行命令的责任分割开,委派给不同的对象。

每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。

二、 命令模式的结构

命令模式的类图如下:

命令模式涉及到五个角色,它们分别是:

  • 客户(Client)角色:创建了一个具体命令(ConcreteCommand)对象并确定其接收者。
  • 命令(Command)角色:声明了一个给所有具体命令类的抽象接口。这是一个抽象角色。
  • 具体命令(ConcreteCommand)角色:定义一个接受者和行为之间的弱耦合;实现Execute()方法,负责调用接收考的相应操作。Execute()方法通常叫做执方法。
  • 请求者(Invoker)角色:负责调用命令对象执行请求,相关的方法叫做行动方法。
  • 接收者(Receiver)角色:负责具体实施和执行一个请求。任何一个类都可以成为接收者,实施和执行请求的方法叫做行动方法。

三、 命令模式的示意性源代码

// Command pattern -- Structural example  
using System;

// "Command"
abstract class Command
{
  // Fields
  protected Receiver receiver;

  // Constructors
  public Command( Receiver receiver )
  {
    this.receiver = receiver;
  }

  // Methods
  abstract public void Execute();
}

// "ConcreteCommand"
class ConcreteCommand : Command
{
  // Constructors
  public ConcreteCommand( Receiver receiver ) :
    base ( receiver ) {}

  // Methods
  public override void Execute()
  {
    receiver.Action();
  }
}

// "Receiver"
class Receiver
{
  // Methods
  public void Action()
  {
    Console.WriteLine("Called Receiver.Action()");
  }
}

// "Invoker"
class Invoker
{
  // Fields
  private Command command;

  // Methods
  public void SetCommand( Command command )
  {
    this.command = command;
  }

  public void ExecuteCommand()
  {
    command.Execute();
  }
}

/**//// <summary>
///  Client test
/// </summary>
public class Client
{
  public static void Main( string[] args )
  {
    // Create receiver, command, and invoker
    Receiver r = new Receiver();
    Command c = new ConcreteCommand( r );
    Invoker i = new Invoker();

    // Set and execute command
    i.SetCommand(c);
    i.ExecuteCommand();
  }
}

四、 玉帝传美猴王上天

命令模式不是新的发明,在美猴王大闹天宫之前就有了。那时玉帝命令太白金星召美猴王上天:"金星径入(水帘洞)当中,面南立定道:'我是西方太白金星,奉玉帝招安圣旨,下界请你上大,拜受仙录。'"玉帝是系统的客户端,太白金星是命令的发出者,猴王是命令的接收者,圣旨就是命令。玉帝的这一道命令就是要求猴王到上界报到。玉帝只管发出命令,而不管命令是怎样传达到美猴王的。太白金星负责将圣旨传到,可是美猴王怎么执行圣旨、何时执行圣旨是美猴王自己的事。果不然,个久美猴王就大闹了天宫。

这个模拟系统的设计如下:

五、 命令模式的实现

首先命令应当"重"一些还是"轻"一些。在不同的情况下,可以做不同的选择。如果把命令设计得"轻",那么它只是提供了一个请求者和接收者之间的耦合而己,命令代表请求者实现请求。

相反,如果把命令设计的"重",那么它就应当实现所有的细节,包括请求所代表的操作,而不再需要接收者了。当一个系统没有接收者时,就可以采用这种做法。

更常见的是处于最"轻"和最"重"的两个极端之间时情况。命令类动态地决定调用哪一个接收者类。

其次是否支持undo和redo。如果一个命令类提供一个方法,比如叫unExecute(),以恢复其操作的效果,那么命令类就可以支持undo和redo。具体命令类需要存储状态信息,包括:

1. 接收者对象实际上实施请求所代表的操作;
2. 对接收者对象所作的操作所需要的参数;
3. 接收者类的最初的状态。接收者必须提供适当的方法,使命令类可以通过调用这个方法,以便接收者类恢复原有状态。

如果只需要提供一层的undo和redo,那么系统只需要存储最后被执行的那个命令对象。如果需要支持多层的undo和redo,那么系统就需要存储曾经被执行过的命令的清单,清单能允许的最大的长度便是系统所支持的undo和redo的层数。沿着清单逆着执行清单上的命令的反命令(unExecute())便是undo;沿着清单顺着执行清单上的命令便是redo。

六、 命令模式的实际应用案例

下面的代码使用命令模式演示了一个简单的计算器,并允许执行undo与redo。注意:"operator"在C#中是关键词,所以在前面添加一个"@"将其变为标识符。

// Command pattern -- Real World example  
using System;
using System.Collections;

// "Command"
abstract class Command
{
  // Methods
  abstract public void Execute();
  abstract public void UnExecute();
}

// "ConcreteCommand"
class CalculatorCommand : Command
{
  // Fields
  char @operator;
  int operand;
  Calculator calculator;

  // Constructor
  public CalculatorCommand( Calculator calculator,
    char @operator, int operand )
  {
    this.calculator = calculator;
    this.@operator = @operator;
    this.operand = operand;
  }

  // Properties
  public char Operator
  {
    set{ @operator = value; }
  }

  public int Operand
  {
    set{ operand = value; }
  }

  // Methods
  override public void Execute()
  {
    calculator.Operation( @operator, operand );
  }
 
  override public void UnExecute()
  {
    calculator.Operation( Undo( @operator ), operand );
  }

  // Private helper function
  private char Undo( char @operator )
  {
    char undo = ' ';
    switch( @operator )
    {
      case '+': undo = '-'; break;
      case '-': undo = '+'; break;
      case '*': undo = '/'; break;
      case '/': undo = '*'; break;
    }
    return undo;
  }
}

// "Receiver"
class Calculator
{
  // Fields
  private int total = 0;

  // Methods
  public void Operation( char @operator, int operand )
  {
    switch( @operator )
    {
      case '+': total += operand; break;
      case '-': total -= operand; break;
      case '*': total *= operand; break;
      case '/': total /= operand; break;
    }
    Console.WriteLine( "Total = {0} (following {1} {2})",
      total, @operator, operand );
  }
}

// "Invoker"
class User
{
  // Fields
  private Calculator calculator = new Calculator();
  private ArrayList commands = new ArrayList();
  private int current = 0;

  // Methods
  public void Redo( int levels )
  {
    Console.WriteLine( "---- Redo {0} levels ", levels );
    // Perform redo operations
    for( int i = 0; i < levels; i++ )
      if( current < commands.Count - 1 )
        ((Command)commands[ current++ ]).Execute();
  }

  public void Undo( int levels )
  {
    Console.WriteLine( "---- Undo {0} levels ", levels );
    // Perform undo operations
    for( int i = 0; i < levels; i++ )
      if( current > 0 )
        ((Command)commands[ --current ]).UnExecute();
  }

  public void Compute( char @operator, int operand )
  {
    // Create command operation and execute it
    Command command = new CalculatorCommand(
      calculator, @operator, operand );
    command.Execute();

    // Add command to undo list
    commands.Add( command );
    current++;
  }
}

/**//// <summary>
/// CommandApp test
/// </summary>
public class Client
{
  public static void Main( string[] args )
  {
    // Create user and let her compute
    User user = new User();

    user.Compute( '+', 100 );
    user.Compute( '-', 50 );
    user.Compute( '*', 10 );
    user.Compute( '/', 2 );

    // Undo and then redo some commands
    user.Undo( 4 );
    user.Redo( 3 );
  }
}

七、 在什么情况下应当使用命令模式

在下面的情况下应当考虑使用命令模式:

1、使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。

2、需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。

3、系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。

4、如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。

5、一个系统需要支持交易(Transaction)。一个交易结构封装了一组数据更新命令。使用命令模式来实现交易结构可以使系统增加新的交易类型。

八、 使用命令模式的优点和缺点

命令允许请求的一方和接收请求的一方能够独立演化,从而且有以下的优点:

  • 命令模式使新的命令很容易地被加入到系统里。
  • 允许接收请求的一方决定是否要否决(Veto)请求。
  • 能较容易地设计-个命令队列。
  • 可以容易地实现对请求的Undo和Redo。
  • 在需要的情况下,可以较容易地将命令记入日志。
  • 命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。
  • 命令类与其他任何别的类一样,可以修改和推广。
  • 你可以把命令对象聚合在一起,合成为合成命令。比如宏命令便是合成命令的例子。合成命令是合成模式的应用。
  • 由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。

命令模式的缺点如下:

  • 使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。

转载于:https://www.cnblogs.com/Mayvar/archive/2011/09/08/wanghonghua_201109080326.html

命令(Command)模式相关推荐

  1. 设计模式学习笔记——命令(Command)模式

    设计模式学习笔记--命令(Command)模式 @(设计模式)[设计模式, 命令模式, command] 设计模式学习笔记命令Command模式 基本介绍 命令案例 类图 实现代码 Command接口 ...

  2. 设计模式--命令(Command)模式

    模式定义 将一个请求(行为)封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作 类图 要点总结 Command模式的根本目的在于将"行为请 ...

  3. python中command是什么意思_python 设计模式之命令(Command)模式

    #写在前面 也了解了不少设计模式了,他们都有一个通病,那就是喜欢把简单的东西复杂化.比如在不同的类中加个第三者.哈哈哈,简单变复杂是有目的的,那就是降低耦合度,增强可维护性,提高代码复用性,使代码变得 ...

  4. C++之命令(Command)模式

    0. 简介 我们在遇到类似GUI中多种Button点击功能的操作时,我们应该将请求的所有细节 (例如调用的对象. 方法名称和参数列表) 抽取出来组成命令类, 该类中仅包含一个用于触发请求的方法. 命令 ...

  5. .NET中的Command(命令)模式

    在Command(命令)模式中,使用对象来表示和封装稍后调用该方法所需的全部信息--这些信息包括方法名.拥有该方法的对象.及参数值.在系列文章".NET中的Command(命令)模式&quo ...

  6. php yii 命令行,命令行模式(Command)

    命令行模式(Command) 3.2.1. 目的 为了封装调用和解耦. 我们有一个调用程序和一个接收器. 这种模式使用「命令行」将方法调用委托给接收器并且呈现相同的「执行」方法. 因此,调用程序只知道 ...

  7. 命令模式(Command模式)详解

    在软件开发系统中,常常出现"方法的请求者"与"方法的实现者"之间存在紧密的耦合关系.这不利于软件功能的扩展与维护.例如,想对行为进行"撤销.重做.记录 ...

  8. Command(命令)模式

    目录 前言 一.命令模式总结 1.模式意图 2.参与者 3.结构.协作.适用性及效果 二.命令模式简单示例 1.用例描述 2.示例代码 总结 前言 本周五参加了Command(命令 )模式研讨会,本文 ...

  9. Command模式的实践

    Command模式的实践 主题 文章将通过一个简单的 Shell  库来阐述 Command模式的一个实际应用. 如何使用 Shell 库 在开始之前,我们先来看看如何使用这个 Shell 库. 解决 ...

  10. 使用Command模式实现撤销机制 (Code Project 精选翻译)

    使用Command模式实现撤销机制[1] Written by Matt Berther Translated by Allen Lee[2] Reviewed by Teddy Tam & ...

最新文章

  1. 苹果签名分发系统需要什么配置的服务器呢,苹果/IOS超级签名分发系统
  2. Ardino基础教程 18_驱动74HC595
  3. 榴莲还有奶油、花生味 印尼出新品售价近7000人民币
  4. notepad++每行首尾添加内容
  5. chown r mysql ._chown -R 用户名:组名 ./ 及 chown用法介绍
  6. 填补服务机器人市场空白,九号机器人提供智能服务场景解决方案!
  7. SpringCloudConfig配置中心读取本地配置文件
  8. js 浮点数精度问题 可以用accounting.js解决
  9. C/C++ 工具函数 —— 大端模式和小端模式的互换
  10. Zookeeper 客户端 Curator 使用详解
  11. [BUUCTF]REVERSE——相册
  12. QQ在线客服代码(绝对强制对话框)
  13. 树莓派CM4 装系统和换源
  14. 爱奇艺“技术气氛组”上线!点击查收你的专属AI拜年新姿势
  15. 手把手的教你安装PyCharm --Pycharm安装详细教程(一)(非常详细,非常....)
  16. LOJ 534 花团(线段树+dfs栈)
  17. 贪心算法——汽车加油问题
  18. __attribute__中的constructor和destructor
  19. Python JPG图片转DCM
  20. label标签的常用作用

热门文章

  1. 数组中常用几种的Arrays方法
  2. java get 和post_[Java教程]get和post方法的区别
  3. 设计模式之GOF23解释器模式
  4. java多线程-线程池
  5. centos7.x 网卡自动关闭,总是需要手动去启动情况解决方法
  6. Centos7.x 安装Kubernetes(K8s) 1.16.2 kubeadm kubelet kubectl 单机版 2019-10-20更新
  7. SpringBoot2 Spring Cloud Config Server和Config Client分布式配置中心使用教程
  8. 诛仙3饮马江南服务器信息,横刀云渺,饮马天河----浅谈云渺天河5S单刷
  9. 小D课堂 - 零基础入门SpringBoot2.X到实战_第4节 Springboot2.0单元测试进阶实战和自定义异常处理_19、SpringBoot个性化启动banner设置debug日志...
  10. 阶段1 语言基础+高级_1-3-Java语言高级_08-JDK8新特性_第1节 常用函数接口_15_常用的函数式接口_Predicate接口练习-集合信息的筛选...