设计模式——命令模式

    看了很多命令模式方面的资料,感觉书上那个餐厅吃饭的例子最好了。确实这个餐厅的例子在一定层面上很好的解释了命令模式以及命令模式的真正用途,所以下面也还是从这个餐厅来说起。       

首先来看一幅图:


    上面这副类图反映的是,一个顾客去一个烤羊肉串的地摊上吃东西(众所周知,地摊上一般就一个厨师)。这样的话,顾客如果想点单(直接在客户端进行点单)的话,其会直接和厨师说,说要点什么什么食品即客户下订单,等客户点完食品之后,厨师就会去做食品了。

    上面的厨师类Barbecue 中呢,定义了两个方法,分别是烤羊肉串、烤鸡翅。

    如果,有一天,这个地摊生意特别好,来了很多顾客,人山人海。每个人都点了一大通食品,而这个厨师就一个人,它要接收订单,又要做食品,估计他也手忙脚乱了。

    在 OO 设计原则中,很明显,这又违背了单一职责原则,因为厨师忙不过来。所以,它有可能记错订单。把顾客 A 要的5串羊肉查记成了要3串,而把顾客 B 要的鸡翅写成了羊肉串,还有一点就是,由于忙不过来。有可能厨师把头给忙昏了,鸡翅才熟了一半就拿出了烤箱,羊肉串没放盐。这样的话,顾客会很不满意的。

    上面的这个例子呢,反映的就是在软件系统中,“行为请求者”和“行为执行者”通常呈现一种紧耦合。在上面的例子中,顾客就是”行为的请求者“,而厨师是“行为执行者”,二者之间是紧耦合。

    有时候,这种紧耦合将是致命的问题,上面的例子就反映出来了,最后,顾客等了半天得不到想要的产品。而厨师呢估计是被顾客骂死了。

    那么如何才能解决上面出现的这个问题呢,由于上面的问题是因为“行为请求者“和”执行请求者“之间紧耦合所致。

    所以,我们应该从如何将”行为请求者“和”行为执行者“解耦下手:

    而对于客户过多时会造成厨师忙不过来的情况的问题,是可以这样考虑的。因为厨师集烤肉和接订单这两大功能于一体,很明显,针对厨师来说,是违背了单一职责原则的。所以需要将这两项功能分离开来,常见的做法,当然就是增加服务员了。可以由服务员来接订单,然后由服务员将订单传递给厨师,厨师就可以按照这份订单来做食品了。

继续往下谈论的话,顾客下了订单后,它不必知道为他做食品的是哪一个厨师(顾客尽管下单就 OK 了,反正有人给他做出来的)。而服务员的话,是用来传递订单的。而厨师接了订单之后,它也不需要知道他在为那个客户做食品。至于如何做食品,用什么原料做食品那就是厨师自己的事情了。

下边是改进后的结构图


    通过在顾客和厨师之间添加一个服务员,用来解除顾客和厨师之间的紧耦合。就是由顾客对服务员说要点什么食品,然后服务员根据顾客点的食品来生成制作食品的命令(也就是订单)。比如,顾客点了羊肉串,那么服务员便会调用 SeOrder来设置一个命令,即BakeMuttonCommand命令。而后,服务员便会调用 Notify方法来通知厨师有一个烹饪命令到达。

下边是上边Demo的实现

<span style="font-family:KaiTi_GB2312;font-size:18px;">        //抽象命令
        public abstract class Command
        {
            protected Barbecuer receiver;
            public Command(Barbecuer receiver)
            {
                this.receiver = receiver;
            }
            //执行命令
            abstract public void ExcuteCommand();
        }
        //具体命令类
        //烤羊肉串命令
        class BakeMuttonCommand : Command
        {
            public BakeMuttonCommand(Barbecuer receiver)
                : base(receiver)
            { }
            public override void ExcuteCommand()
            {
                receiver .BakeMutton ();
            }
        }
        //烤鸡翅命令
        class BakeChickenWingCommand : Command
        {
            public BakeChickenWingCommand(Barbecuer receiver)
                : base(receiver)
            { }
            public override void ExcuteCommand()
            {
                receiver .BakeChickenWing ();
            }
        }

        //服务员
        public class Waiter
        {
            private Command command;
            //设置订单
            public void SetOrder(Command command)
            {
                this.command = command;
            }
            //通知执行
            public void Notify()
            {
                command.ExcuteCommand();
            }

        }
        //烤肉者
        public class Barbecuer
        {
            //烤羊肉
            public void BakeMutton()
            {
                Console.WriteLine("烤羊肉串");
            }
            //烤鸡翅
            public void BakeChickenWing()
            {
                Console.WriteLine("烤鸡翅");
            }
        }</span>
客户端代码:

<span style="font-family:KaiTi_GB2312;font-size:18px;">       static void Main(string[] args)
        {
            //开店前的准备
            Barbecuer boy = new Barbecuer();
            Command bakeMuttonCommand1 = new BakeMuttonCommand(boy );
            Command bakeMuttonCommand2 = new BakeMuttonCommand(boy);
            Command bakeChickenWingCommand1 = new BakeChickenWingCommand(boy);
            Waiter girl = new Waiter();

            //开门营业
            girl.SetOrder(bakeChickenWingCommand1);
            girl.Notify();
            girl.SetOrder(bakeMuttonCommand2);
            girl.Notify();
            girl.SetOrder(bakeMuttonCommand1);
            girl.Notify();


            Console.Read();
           </span>
以上就是一个典型的命令模式的应用。
下边这是命令模式的结构图:

 

下面就来介绍一下在命令模式中的各个角色:

首先是客户(Client):客户创建一个具体的命令对象并且要确定其接收者。

抽象命令角色(AbstractCommand):声明一个给所有具体命令类的抽象接口。

具体命令角色(ConcreteCommand):定义一个接收者和行为之间的弱耦合,并且要实现 Execute 方法,来负责调用接收者的相应操作。

请求者角色(Invoker):负责调用命令对象执行请求。

接收者角色(Receiver):负责具体实施和执行一个请求。

而在餐馆模型中,上面提到的角色也是很明确的,

客户就是顾客,抽象命令角色就是抽象厨艺命令类—Command,

而具体命令角色就是具体厨艺命令类—如BakeMuttonCommand 等,

请求者角色则是由服务员扮演,最后接收者就是由厨师来扮演了。

命令模式的定义:

    命令模式,命令模式把一个请求或者是一个操作封装到一个对象中,而成使你可以用不同的请求对客户端参数化,也可以实现对请求排队或者是记录请求日志,同时还可以提供命令的撤销和恢复功能。

    命令模式是对命令的封装,命令模式把发出命令的责任和执行命令这两个功能块分离,将这两个功能分别委托给不同的对象。每一个命令都是一个操作:请求的一方发出请求其要求执行一个操作,接收的一方收到请求,并执行操作,

    命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求时怎么被接收,以及操作是否被执行,何时被执行,以及时怎么被执行的。

当然,大名鼎鼎的命令模式还有其他的优点:

队列请求和撤销请求

这里使用还是利用前面的餐馆模型来分析,在前面制作出来的 Demo 其实有不足的地方的,那就是,你每一次都只能保存一个命令,也就是每一张订单上都只有一道食品。如果一个顾客要点三道菜的话,那请求者还得同时发三个请求才能实现一名顾客点多道菜。同时,如果用户点了食品后突然又不想要这份食品了,那么就需要撤销先前点的这份食品(也就是撤销命令),所以必须改进,这里只需要修改 Waiter (在命令模式中扮演请求者 Invoker 这个角色)类和 Main 函数就 OK 了。

修改成如下:

<span style="font-family:KaiTi_GB2312;font-size:18px;">        //服务员
        public class Waiter
        {
            //增加存放具体命令的容器
            private IList<Command> orders = new List<Command>();            
            //设置订单
            public void SetOrder(Command command)
            {
                //在客户提出请求时,对没货的烧烤进行回绝
                if (command.ToString() == "命令模式.BakeChickenWingCommand")
                {
                    Console.WriteLine("服务员:对不起,鸡翅没有了。");
                }
                else
                {
                    orders.Add(command);
                    Console.WriteLine("增加订单:"+command .ToString ()+"时间:"+DateTime .Now .ToString ());
                }
            }

            //取消订单
            public void CancelOrder(Command command)
            {
                orders.Remove(command);
                Console.WriteLine("取消订单:"+command .ToString ()+"时间:"+DateTime .Now .ToString ());
            }
            //通知执行
            public void Notify()
            {
                foreach (Command cmd in orders)
                {
                    cmd.ExcuteCommand();
                }
            }
        }</span>
<span style="font-family:KaiTi_GB2312;font-size:18px;">namespace 命令模式
{
    class Program
    {
        static void Main(string[] args)
        {
            //开店前的准备
            Barbecuer boy = new Barbecuer();
            Command bakeMuttonCommand1 = new BakeMuttonCommand(boy );
            Command bakeMuttonCommand2 = new BakeMuttonCommand(boy);
            Command bakeChickenWingCommand1 = new BakeChickenWingCommand(boy);
            Waiter girl = new Waiter();

            //开门营业
            girl.SetOrder(bakeChickenWingCommand1);           
            girl.SetOrder(bakeMuttonCommand2);
            girl.SetOrder(bakeMuttonCommand1);

            //点菜完毕,通知厨房
            girl.Notify();

            Console.Read();
        }</span>

下面再来总结一下命令模式的特点:

一,命令模式将发出请求的对象(Invoker)和执行请求的对象(Receiver)解耦。

二,发出请求的对象和执行请求的对象之间是通过命令对象进行沟通的。

三,命令对象封装了一个或者是一组动作。

四,命令模式比较容易实现一个命令队列(上面改进后实现一次订单多个命令就是如此)。

五,允许是否要撤销命令的执行(比如点菜后又不点这个菜了)。

六,比较容易实现对请求的撤销和重做(比如我点菜后又不点了,可想了一下还是点这个菜吧)。

七,由于对命令类进行了抽象,所以增加新的具体命令类非常容易。

八,可以比较方便的实现日志请求。

九,命令模式一个比较明显的缺点就是会导致系统有过多的具体命令类(鬼晓得会有多少种命令啊)。


评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值