• 设计模式 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)

  • 相关阅读:
    Java_Activiti5_菜鸟也来学Activiti5工作流_之入门简单例子(一)
    Java_Activiti5_菜鸟也来学Activiti5工作流_之初识BPMN2.0的简单结构(五)
    Java_Activiti5_菜鸟也来学Activiti5工作流_之JUnit单元测试(四)
    Java_Activiti5_菜鸟也来学Activiti5工作流_之与Spring集成(三)
    Java_Activiti5_菜鸟也来学Activiti5工作流_之初识常用服务类和数据表(二)
    Html+Css+Js_之table每隔3行显示不同的两种颜色
    Java使用poi对Execl简单操作_总结
    Java使用poi对Execl简单_写_操作
    Java使用poi对Execl简单_读_操作
    Java使用poi对Execl简单_读和写_操作
  • 原文地址:https://www.cnblogs.com/wzh206/p/1690798.html
Copyright © 2020-2023  润新知