设计模式 Command

本文是多篇文章的整理

 

命令模式意图:
  GOF 在《设计模式》一书中阐述其意图:“将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。”这里所谓的“不同的请求”也既意味着请求可能发生的变化,是一个可能扩展的功能点。

命令模式UML图:
         
  Command模式将一个请求封装为一个对象,从而使你可以使用不同的请求对客户进行参数化。
在什么情况下应当使用命令模式
在下面的情况下应当考虑使用命令模式:
1、使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。
2、需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
3、系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
4、如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
5、一个系统需要支持交易(Transaction)。一个交易结构封装了一组数据更新命令。使用命令模式来实现交易结构可以使系统增加新的交易类型。

使用命令模式的优点和缺点
命令允许请求的一方和接收请求的一方能够独立演化,从而且有以下的优点:
命令模式使新的命令很容易地被加入到系统里。
允许接收请求的一方决定是否要否决(Veto)请求。
能较容易地设计-个命令队列。
可以容易地实现对请求的Undo和Redo。
在需要的情况下,可以较容易地将命令记入日志。
命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。
命令类与其他任何别的类一样,可以修改和推广。
你可以把命令对象聚合在一起,合成为合成命令。比如宏命令便是合成命令的例子。合成命令是合成模式的应用。
由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。
命令模式的缺点如下:
使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。


简单示例:

Client
 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4
 5namespace DesignPattern.Command
 6{
 7    class Program
 8    {
 9        static void Main(string[] args)
10        {
11            // 创建receiver、command和invoker 
12            Receiver receiver = new Receiver();
13
14            //根据多态,父类的引用指向子类对象
15            Command command = new ConcreteCommand(receiver);
16            Invoker invoker = new Invoker();
17
18            //设置和执行命令
19            invoker.SetCommand(command);
20            invoker.ExecuteCommand();
21
22            Console.Read();
23        }

24    }

25}

26

Command
 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4
 5namespace DesignPattern.Command
 6{
 7    public abstract class Command
 8    {
 9        protected Receiver receiver;
10
11         /// <summary>
12        /// 构造器注入
13        /// </summary>
14        /// <param name="receiver"></param>

15        public Command(Receiver receiver)
16        {
17            this.receiver = receiver;
18        }

19
20        public abstract void Execute();
21    }

22}

23

Invoker
 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4
 5namespace DesignPattern.Command
 6{
 7    public class Invoker
 8    {
 9        private Command command;
10
11        public void SetCommand(Command command)
12        {
13            this.command = command;
14        }

15
16        public void ExecuteCommand()
17        {
18            command.Execute();
19        }

20    }

21}

22

Receiver
 1using System;
 2using System.Collections.Generic;
 3using System.Text;
 4
 5namespace DesignPattern.Command
 6{
 7    public class Receiver
 8    {
 9        public void Action()
10        {
11            Console.WriteLine("Called Receiver.Action()");
12        }

13    }

14}

15

运行结果:

Called Receiver.Action()


 
    在众多的设计模式中,Command模式是很简单也很优雅的一种设计模式。Command模式它封装的是命令,把命令发出者的责任和命令执行者的责任分开。[TerryLee]

注意:
    如果比较类图结构,我门会发现Command模式、Strategy模式、State模式是完全一样的。事实正是如此,由于他门的设计思想都是对易于变化的部分进行抽象、或为接口。唯一的区别,就是所抽象的行为职责不同而已,这一点从各自的名字就可以看出。

参考资料:
TerryLee------.NET设计模式系列
Bruce Zhang---《软件设计精要与模式》

============================================================================================================================================

This code demonstrates the Command pattern used in a simple calculator with unlimited number of undo's and redo's. Note that in C#  the word 'operator' is a keyword. Prefixing it with '@' allows using it as an identifier.

 

// Command pattern -- Real World example

using System;

using System.Collections.Generic;

 

namespace DoFactory.GangOfFour.Command.RealWorld

{

  /// <summary>

  /// MainApp startup class for Real-World

  /// Command Design Pattern.

  /// </summary>

  class MainApp

  {

    /// <summary>

    /// Entry point into console application.

    /// </summary>

    static void Main()

    {

      // Create user and let her compute

      User user = new User();

 

      // User presses calculator buttons

      user.Compute('+', 100);

      user.Compute('-', 50);

      user.Compute('*', 10);

      user.Compute('/', 2);

 

      // Undo 4 commands

      user.Undo(4);

 

      // Redo 3 commands

      user.Redo(3);

 

      // Wait for user

      Console.ReadKey();

    }

  }

 

  /// <summary>

  /// The 'Command' abstract class

  /// </summary>

  abstract class Command

  {

    public abstract void Execute();

    public abstract void UnExecute();

  }

 

  /// <summary>

  /// The 'ConcreteCommand' class

  /// </summary>

  class CalculatorCommand : Command

  {

    private char _operator;

    private int _operand;

    private Calculator _calculator;

 

    // Constructor

    public CalculatorCommand(Calculator calculator,

      char @operator, int operand)

    {

      this._calculator = calculator;

      this._operator = @operator;

      this._operand = operand;

    }

 

    // Gets operator

    public char Operator

    {

      set { _operator = value; }

    }

 

    // Get operand

    public int Operand

    {

      set { _operand = value; }

    }

 

    // Execute new command

    public override void Execute()

    {

      _calculator.Operation(_operator, _operand);

    }

 

    // Unexecute last command

    public override void UnExecute()

    {

      _calculator.Operation(Undo(_operator), _operand);

    }

 

    // Returns opposite operator for given operator

    private char Undo(char @operator)

    {

      switch (@operator)

      {

        case '+': return '-';

        case '-': return '+';

        case '*': return '/';

        case '/': return '*';

        default: throw new

         ArgumentException("@operator");

      }

    }

  }

 

  /// <summary>

  /// The 'Receiver' class

  /// </summary>

  class Calculator

  {

    private int _curr = 0;

 

    public void Operation(char @operator, int operand)

    {

      switch (@operator)

      {

        case '+': _curr += operand; break;

        case '-': _curr -= operand; break;

        case '*': _curr *= operand; break;

        case '/': _curr /= operand; break;

      }

      Console.WriteLine(

        "Current value = {0,3} (following {1} {2})",

        _curr, @operator, operand);

    }

  }

 

  /// <summary>

  /// The 'Invoker' class

  /// </summary>

  class User

  {

    // Initializers

    private Calculator _calculator = new Calculator();

    private List<Command> _commands = new List<Command>();

    private int _current = 0;

 

    public void Redo(int levels)

    {

      Console.WriteLine("\n---- Redo {0} levels ", levels);

      // Perform redo operations

      for (int i = 0; i < levels; i++)

      {

        if (_current < _commands.Count - 1)

        {

          Command command = _commands[_current++];

          command.Execute();

        }

      }

    }

 

    public void Undo(int levels)

    {

      Console.WriteLine("\n---- Undo {0} levels ", levels);

      // Perform undo operations

      for (int i = 0; i < levels; i++)

      {

        if (_current > 0)

        {

          Command command = _commands[--_current] as Command;

          command.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++;

    }

  }

}


Output
Current value = 100 (following + 100)
Current value =  50 (following - 50)
Current value = 500 (following * 10)
Current value = 250 (following / 2)

---- Undo 4 levels
Current value = 500 (following * 2)
Current value =  50 (following / 10)
Current value = 100 (following + 50)
Current value =   0 (following - 100)

---- Redo 3 levels
Current value = 100 (following + 100)
Current value =  50 (following - 50)
Current value = 500 (following * 10)

原文地址:https://www.cnblogs.com/wzh206/p/1690798.html