设计模式之命令模式、举例分析、通俗易懂

1. 定义

命令模式(Command):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作

简单来说,就是类似于消费者-服务员-厨师,消费者要点餐,告诉了服务员其需要的食物,此时服务员就会告诉厨师,如果是服务员亲自接触厨师,就会造成其紧耦合,每次厨师添加新菜都需要告诉服务员,让其服务员类里需要变动;但是如果同过命令的方式,服务员发布命令,命令则调用厨师对应方法,后续增加厨师的新菜也只需要添加对应的命令而已,但是服务员是不会变的,因为其调用的是抽象的命令

2. 举例分析

为什么要用命令模式呢?怎么用?
题目:
在一家烧烤摊,其业务是烤羊肉串和烤鸡翅,厨师就是服务员,顾客点菜需要直接告诉厨师,然后厨师做对应的烧烤

这样的话,就会导致顾客和厨师的紧耦合,如下图:
在这里插入图片描述
程序:
在这里插入图片描述

在这里插入图片描述在这里插入图片描述

如果人一多的话,请求多了,就容易乱,而且其没有记录,全靠厨师记忆,而且如果我反悔不要某烧烤也不能撤回,这就是路边摊模式;为了改变这个现象,我们让厨师其服务员职责分离,即厨师不负责和顾客接触,只负责烧烤,而服务员则和顾客接触

如下题目

3. 以命令模式来编写例子

题目:
在一家烧烤厅,其业务是烤羊肉串和烤鸡翅,顾客需要点单告诉服务员,服务员得到需求后则会通过自己的方式告诉厨师,让厨师做对应的烧烤

分析:
顾客只要联系服务员即可,也就是说顾客当做客户端,调用服务员;服务员和厨师联系为了防止紧耦合,不然厨师类了方法,服务员类也需要改,所以以一个中介即填写好的菜单给厨师,也就是命令,服务员调用抽象命令类,而具体命令类则调用厨师的具体方法,这样就防止了紧耦合,也就是命令模式

结构图:
在这里插入图片描述
代码:

  1. 厨师(烧烤串者)
 //厨师
    class Chef
    {
        public void BakeMutton()
        {
            Console.WriteLine("烤羊肉串!");
        }
        public void BakeChickenWing()
        {
            Console.WriteLine("烤鸡翅!");
        }
    }
  1. 抽象命令类
	abstract class Command
    {
        protected readonly Chef chef;
		//构造函数的目的是将需要的厨师代入进来,也就是依赖关系,服务员依赖于某个厨师,同时也是为了方便子类调用某个厨师的某个烹饪方法,不然子类需要自己来构造函数,不如继承,反正共用
        public Command(Chef chef)
        {
            this.chef = chef;
        }
        //一个抽象函数,目的是为了给子类提供一个共同调用厨师的方法
        public abstract void Excute();
    }
  1. 具体命令类,烤羊肉串类和烤鸡翅类
//具体命令类:烤羊肉
    class BakeMuttonCommand : Command
    {
        public BakeMuttonCommand(Chef chef) : base(chef)
        {
        }

        public override void Excute()
        {
            chef.BakeMutton();//也就是说这个具体类命令的Excute()方法调用了厨师对应的方法,这个调用方法(中介)是具体命令类和抽象类的核心
        }
    }
    //具体命令类:烤鸡翅
    class BakeChichenWingCommand : Command
    {
        public BakeChichenWingCommand(Chef chef) : base(chef)
        {
        }

        public override void Excute()
        {
            chef.BakeChickenWing();//也就是说这个具体类命令的Excute()方法调用了厨师对应的方法,这个调用方法(中介)是具体命令类和抽象类的核心
        }
    }
  1. 服务员类
class Waiter
    {
    //服务员调用抽象命令类就好,一个方法将服务员类与命令类联系起来,是聚合关系;另一个方法是服务员调用命令类的执行方法,这个命令类的执行方法则会让厨师执行对应的方法
    //这两个方法是服务员类的核心
        public Command command;
        public void SetCommand(Command command)
        {
            this.command = command;
        }
        public void UseCommand()
        {
            command.Excute();
        }
    }
  1. 客户端调用
static void Main(string[] args)
        {
            Chef chef = new Chef();//建立厨师的实例
            Command command1 = new BakeMuttonCommand(chef);//让该厨师做第一份烤羊肉的命令
            Command command2 = new BakeMuttonCommand(chef);//让该厨师做第二份烤羊肉的命令
            Command command3 = new BakeChichenWingCommand(chef);//让该厨师做烤鸡翅的命令
            Waiter waiter = new Waiter();//建立服务员的实例

            //让服务员记单,只能一次一次下命令,下了一个命令需要立刻执行,不然会冲突掉,因为我们当时是拿了个字段来存命令,而不是list
            waiter.SetCommand(command1);
            waiter.UseCommand();

            waiter.SetCommand(command2);
            waiter.UseCommand();

            waiter.SetCommand(command3);
            waiter.UseCommand();

            Console.Read();
        }

结果:
在这里插入图片描述

缺点:
这个命令模式还不完整,因为顾客点餐时候不会刚点一个服务员就让厨师去做,而是所有的单点完后,才会去找厨师让一起做,所以我们就必须用list这种集合类型来存储命令

另外,如果某个菜已经没有材料了,服务员应该在客户点该菜时候,告诉客户其没该菜

同时,客户点了那些烧烤需要记录下来,跟记录日志一样,以备收费

最后,如果顾客点的太多,临时退单也是可以的

所以,我们针对上述缺点来进行优化

4. 以命令模式来编写例子升级版

  1. 我们假设烤鸡翅的鸡翅只有2串,如果客户点了超过2串,则服务员告诉顾客,并且厨师因为材料不足是不会制作其烤鸡翅的
  2. 其他类不用变,只需要改变服务员类,将里面的一个字段储存(只能储存一个命令)转换为List类型集合来存储多命令
  3. 添加一个取消订单的方法

服务员代码:

 class Waiter
    {
        public List<Command> commands = new List<Command>();
        private int num = 2;
        public void SetCommand(Command command)
        {
            //判断鸡翅还有没有,里面还剩2串鸡翅,其他的烤羊肉是充足的
            if (command.ToString() == "命令模式可撤销可记录日志可多命令.BakeChichenWingCommand")
            {
                if (num <= 0)
                {
                    Console.WriteLine("服务员:鸡翅没有了,请点其他的烧烤");
                }
                else
                {
                    num--;
                    commands.Add(command);
                    Console.WriteLine("增加订单: 烤鸡翅  "  + "时间: " + DateTime.Now.ToString());
                }
            }
            else
            {
                commands.Add(command);
                Console.WriteLine("增加订单: 烤羊肉串  " + "时间: " + DateTime.Now.ToString());
            }
        }

        //命令全部进行
        public void UseCommand()
        {
            Console.WriteLine("----------分割线----------");
            foreach (var item in commands)
            {
                item.Excute();
            }
        }

        //撤销命令
        public void Revocation(Command command)
        {
            commands.Remove(command);
            Console.WriteLine("撤销订单: " + command.ToString() + "时间: " + DateTime.Now.ToString());
        }
    }

客户端代码:

		static void Main(string[] args)
        {
            Chef chef = new Chef();//建立厨师的实例
            Command command1 = new BakeMuttonCommand(chef);//让该厨师做第一份烤羊肉的命令
            Command command2 = new BakeMuttonCommand(chef);//让该厨师做第二份烤羊肉的命令
            Command command3 = new BakeChichenWingCommand(chef);//让该厨师做烤鸡翅的命令
            Waiter waiter = new Waiter();//建立服务员的实例

            //开始点餐,服务员记录完所有命令后,一起交给厨师,可以撤销命令,另外烤鸡翅只剩下2串
            waiter.SetCommand(command1);//点一串烤羊肉
            waiter.SetCommand(command2);//点一串烤羊肉
            waiter.SetCommand(command3);//点一串鸡翅
            waiter.SetCommand(command3);//点一串鸡翅
            waiter.SetCommand(command3);//点一串鸡翅
            waiter.SetCommand(command1);//点一串烤羊肉
            waiter.Revocation(command1);//撤回一条点烤羊肉的命令
            waiter.UseCommand();//执行所有命令

            Console.Read();
        }

其他的代码不变

程序结果:
在这里插入图片描述

5. 命令模式结构图与模板

结构图:
在这里插入图片描述
代码模板:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6. 例子升级版的全部代码

using System;
using System.Collections.Generic;

namespace 命令模式可撤销可记录日志可多命令
{
    class Program
    {
        static void Main(string[] args)
        {
            Chef chef = new Chef();//建立厨师的实例
            Command command1 = new BakeMuttonCommand(chef);//让该厨师做第一份烤羊肉的命令
            Command command2 = new BakeMuttonCommand(chef);//让该厨师做第二份烤羊肉的命令
            Command command3 = new BakeChichenWingCommand(chef);//让该厨师做烤鸡翅的命令
            Waiter waiter = new Waiter();//建立服务员的实例

            //开始点餐,服务员记录完所有命令后,一起交给厨师,可以撤销命令,另外烤鸡翅只剩下2串
            waiter.SetCommand(command1);//点一串烤羊肉
            waiter.SetCommand(command2);//点一串烤羊肉
            waiter.SetCommand(command3);//点一串鸡翅
            waiter.SetCommand(command3);//点一串鸡翅
            waiter.SetCommand(command3);//点一串鸡翅
            waiter.SetCommand(command1);//点一串烤羊肉
            waiter.Revocation(command1);//撤回一条点烤羊肉的命令
            waiter.UseCommand();//执行所有命令

            Console.Read();
        }
    }

    //1. 命令模式代码
    //抽象命令类
    abstract class Command
    {
        protected readonly Chef chef;

        public Command(Chef chef)
        {
            this.chef = chef;
        }
        public abstract void Excute();
    }
    //具体命令类:烤羊肉
    class BakeMuttonCommand : Command
    {
        public BakeMuttonCommand(Chef chef) : base(chef)
        {
        }

        public override void Excute()
        {
            chef.BakeMutton();
        }
    }
    //具体命令类:烤鸡翅
    class BakeChichenWingCommand : Command
    {
        public BakeChichenWingCommand(Chef chef) : base(chef)
        {
        }

        public override void Excute()
        {
            chef.BakeChickenWing();
        }
    }


    //服务员
    class Waiter
    {
        public List<Command> commands = new List<Command>();
        private int num = 2;
        public void SetCommand(Command command)
        {
            //判断鸡翅还有没有,里面还剩2串鸡翅,其他的烤羊肉是充足的
            if (command.ToString() == "命令模式可撤销可记录日志可多命令.BakeChichenWingCommand")
            {
                if (num <= 0)
                {
                    Console.WriteLine("服务员:鸡翅没有了,请点其他的烧烤");
                }
                else
                {
                    num--;
                    commands.Add(command);
                    Console.WriteLine("增加订单: 烤鸡翅  "  + "时间: " + DateTime.Now.ToString());
                }
            }
            else
            {
                commands.Add(command);
                Console.WriteLine("增加订单: 烤羊肉串  " + "时间: " + DateTime.Now.ToString());
            }
        }

        //命令全部进行
        public void UseCommand()
        {
            Console.WriteLine("----------分割线----------");
            foreach (var item in commands)
            {
                item.Excute();
            }
        }

        //撤销命令
        public void Revocation(Command command)
        {
            commands.Remove(command);
            Console.WriteLine("撤销订单: " + command.ToString() + "时间: " + DateTime.Now.ToString());
        }
    }

    //厨师
    class Chef
    {
        public void BakeMutton()
        {
            Console.WriteLine("烤羊肉串!");
        }
        public void BakeChickenWing()
        {
            Console.WriteLine("烤鸡翅!");
        }
    }
}

7. 命令模式使用背景与作用

第一,它能较容易地设计一个命令队列;

第二,在需要的情况下,可以较容易地将命令记入日志:

第三,允许接收请求的一方决定是否要否决请求。

第四,可以容易地实现对请求的撤销和重做;

第五,由于加进新的具体命令类不影响其他的类,因此增加新的具体命令类很容易。

其实还有最关键的优点就是命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。

那么,是否是碰到类似情况就一定要实现命令模式呢?
这就不一定了,比如命令模式支持撤销/恢复操作功能,但你还不清楚是否需要这个功能时,你要不要实现命令模式?
其实应该是不要实现。敏捷开发原则告诉我们,不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值