14.Command命令(行为型模式)

一、动机(Motivation)

<1>在软件构建过程中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合——比如需要对行为进行“记录、撤销/重做(undo/redo)、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
<2>在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

二、意图(Intent)

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
——《设计模式》GoF

三、结构(Structure)

14.Command命令(行为型模式)_第1张图片

四、结构详解

14.Command命令(行为型模式)_第2张图片

五、生活中的例子

服务员接受顾客的点单,把它记在账单上封装。这个点单被排队等待烹饪。
14.Command命令(行为型模式)_第3张图片
14.Command命令(行为型模式)_第4张图片

六、实现

namespace Test
{
    public class 厨师
    {
        //Action
        public void 烤鸡翅(int 数量)
        {
            Console.WriteLine("厨师烤鸡翅:{0}", 数量);
        }
        //Action
        public void 烤羊肉串(int 数量)
        {
            Console.WriteLine("厨师烤羊肉串:{0}", 数量);
        }
    }

    //Command
    public abstract class 命令
    {
        protected 厨师 _cook; //receiver接受者
        public 命令(厨师 cook)
        {
            this._cook = cook;
        }
        //Execute执行
        public abstract void 烧烤();

        //State状态
        public bool 执行完毕 = false;

        private int _数量;
        public int 数量
        {
            get
            {
                return this._数量;
            }
            set
            {
                if (value < 0)
                {
                    Console.WriteLine("数量应>0");
                }
                else
                {
                    this._数量 = value;
                }
            }
        }
    }

    //ConcreteCommand具体命令
    public class 烤鸡翅命令 : 命令
    {
        public 烤鸡翅命令(厨师 cook) : base(cook)
        {
        }
        public override void 烧烤()
        {
            base._cook.烤鸡翅(base.数量);
            this.执行完毕 = true;
        }
    }
    //ConcreteCommand具体命令
    public class 烤羊肉串命令 : 命令
    {
        public 烤羊肉串命令(厨师 cook) : base(cook)
        {
        }
        public override void 烧烤()
        {
            base._cook.烤羊肉串(base.数量);
            this.执行完毕 = true;
        }
    }

    //Invoker
    public class 服务员
    {
        private int 鸡翅数量 = 60;
        private int 羊肉串数量 = 80;
        private System.Collections.Generic.List<命令> orders = new List<命令>();

        public void 下单(命令 command)
        {
            if (command is 烤鸡翅命令)
            {
                if (this.鸡翅数量 < command.数量)
                {
                    Console.WriteLine("对不起,鸡翅不够了,现在只剩下:{0}", this.鸡翅数量);
                    return;
                }
                else
                {
                    this.鸡翅数量 -= command.数量;
                }
            }
            if (command is 烤羊肉串命令)
            {
                if (this.羊肉串数量 < command.数量)
                {
                    Console.WriteLine("对不起,羊肉串不够了,现在只剩下:{0}", this.鸡翅数量);
                    return;
                }
                else
                {
                    this.羊肉串数量 -= command.数量;
                }
            }
            orders.Add(command);
            Console.WriteLine("新下单:{0},数量:{1}\t{2}", command, command.数量, DateTime.Now);
        }

        public void 取消订单(命令 command)
        {
            if (command.执行完毕)
            {
                Console.WriteLine("订单已执行完毕,东西都吃到肚子里了.不能再取消");
                return;
            }
            orders.Remove(command);
            if (command is 烤鸡翅命令)
            {
                this.鸡翅数量 += command.数量;
            }
            else
            {
                this.羊肉串数量 += command.数量;
            }
            Console.WriteLine("订单已取消:" + command.ToString() + "\t" + DateTime.Now.ToString());
        }

        public void 全部执行()
        {
            foreach (命令 cmd in orders)
            {
                if (!cmd.执行完毕)
                {
                    cmd.烧烤();
                }
            }
        }
    }

    internal class Program
    {
        static void Main(string[] args)
        {
            服务员 mm = new 服务员();
            厨师 cook = new 厨师();

            //30个鸡翅
            命令 cmd30个鸡翅 = new 烤鸡翅命令(cook);
            cmd30个鸡翅.数量 = 30;

            //40个羊肉串 
            命令 cmd40个羊肉串 = new 烤羊肉串命令(cook);
            cmd40个羊肉串.数量 = 40;

            Console.WriteLine("========先来30个鸡翅,40个羊肉串=========");

            mm.下单(cmd30个鸡翅);
            mm.下单(cmd40个羊肉串);

            mm.全部执行();

            Console.WriteLine("========实在太爽了,再来40个鸡翅,50个羊肉串=========");

            //40个鸡翅
            命令 cmd40个鸡翅 = new 烤鸡翅命令(cook);
            cmd40个鸡翅.数量 = 40;

            //50个羊肉串 
            命令 cmd50个羊肉串 = new 烤羊肉串命令(cook);
            cmd50个羊肉串.数量 = 50;

            mm.下单(cmd40个鸡翅);
            mm.下单(cmd50个羊肉串);
            mm.全部执行();

            Console.WriteLine("========不爽了,取消30个鸡翅,40个羊肉串=========");
            mm.取消订单(cmd30个鸡翅);
            mm.取消订单(cmd40个羊肉串);
            Console.ReadLine();
        }
    }
}

实现结果
14.Command命令(行为型模式)_第5张图片

七、适用性

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

八、总结

<1>Command模式是非常简单而又优雅的一种设计模式,它的根本目的在于将“行为请求者”与“行为实现者”解耦;
<2>Command模式的根本目的在于将“行为请求者”与“行为实现者”解耦,在面向对象语言中,常见的实现手段是“将行为抽象为对象”;
<3>实现Command接口的具体命令对象ConcreteCommand有时候根据需要可能会保存一些额外的状态信息;
<4>通过使用Compmosite模式,可以将多个命令封装为一个“复合命令”MacroCommand;
<5>Command模式与C#中的Delegate有些类似。但两者定义行为接口的规范有所区别:Command以面向对象中的“接口-实现”来定义行为接口规范,更严格,更符合抽象原则;Delegate以函数签名来定义行为接口规范,更灵活,但抽象能力比较弱;
<6>使用命令模式会导致某些系统有过多的具体命令类。某些系统可能需要几十个,几百个甚至几千个具体命令类,这会使命令模式在这样的系统里变得不实际。

你可能感兴趣的:(c#设计模式,设计模式)