zoukankan      html  css  js  c++  java
  • 命令模式(head first 设计模式5)

    一、命令模式定义

    命令大家都不会陌生,那么在开始命令模式之前,可以想象一下生活中的命令模式的特点:

    如老板命令你完成一个OA项目是一个命令,接着看看其特点:

    1、在上面的命令中,命令的执行者肯定是聪明的你了。具体的执行方法,可能是通过vs实现,或者是通过eclipse实现,由此看来:命令要有个命令的执行者,还要有个命令的执行方法。

    2、命令的发出者很明显是老板,老板还有个发出方法,可能是通过电话给你说,也可能给你邮件给你说,也可能是通过开会给你说。所以命令的发出者要有一个命令,还要有个发出的方法。

    3、最后看看命令,命令有个名字,命令的肯定要执行。而且命令是在boss给你发出通知后执行的。

    接下来看看命令模式的定义:

    命令模式:将请求封装成对象,以便使用不同的请求、日志、队列等来参数化其他对象。命令模式也支持撤销操作。

    每次讲一个模式时,从定义都不能体会其中的技巧,所以接着我会通过举例子来说明命令模式。

    二、命令模式的举例

    下面来看看多用遥控器是如何使用命令模式的。

    2.1需求

    假设某个公司需要设计一个多用功能的遥控器。基本的需求如下:

    该遥控器有可以控制风扇,白炽灯,热水器等等的多对开关,而且可能还有其他的电器,暂时不做其功能,但是希望可以保留接口,用的时间可以方便的扩展。

    除上面的需求之外,还需要有个按钮,可以撤销上一步的操作。基本功能如下图:

    image

    2.2问题

    在设计遥控器时,风扇,白炽灯,热水器的开关方法已经定义好,其名字各不相同。不妨设置其方法为如下:

    image

    由于各种电器的开关方法都不一样,而且还存在一个待扩展的电器,如果没有学习命名模式之前,我们在设置扩展的开关时,会出现的问题是什么呢?假设现在有电视,冰箱还可能会用到遥控器,那么我们会在最后一个开关上写if else,当然如果哪一天有多了一个大门也加入了我们的遥控的行列,这样我们继续加if else ,很显然随着电器的高速发展,会有多个需要遥控可以控制的。

    举个例子,如果我们是需要遥控的客户,现在有一款遥控如果有遥控可以进行扩展,一种是可以扩展指定类型的,像上面的,只能再去扩展电视和冰箱中的一种,偶尔有一天你看到隔壁邻居的门,也可以使用遥控了,所以你去把你的高级遥控器,拿到扩展店时,扩展工程师说了,现在只能扩展电视和冰箱,不支持对大门的遥控扩展.

    我们肯定是希望,可以自由的扩展,大门可以使用遥控了,就对大门扩展,车门使用遥控了,就对车门扩展……其实也就是一种松耦合的实现。

    2.3分析问题

    为了实现松耦合,我们现在来想一下,周末去请朋友吃饭,服务员mm问你吃什么,你说水煮活鱼,然后在菜单上面,写上水煮活鱼。下个星期天想吃花生米啤酒,同样也写在订单上,然后服务员mm把订单拿给厨师。

    在上面的例子中,无论你点了什么菜,服务员mm,只需要知道顾客点的什么菜,从而给不同的厨师做(虽然不是直接的,但最终凉菜会给凉菜的师傅做,热菜的会给热菜的厨师做),然而具体的怎么做是厨师的事情,服务员知道顾客点上面菜,只是为了能正确的交个厨师去做。

    其实在这个例子中,也是一个命令模式的例子,不同的订单对应的有不同的厨师,最终订单拿到厨师面前,就是对厨师下了个命令,要做菜了。每订单或者说是每种菜都对应着自己的厨师,所以,客服需要有订单的的引用,订单为了知道调用哪个厨师,需要有厨师引用;两个引用都是使用的组合。从而可以调用多种自己需要对象的方法来实现松耦合。这里记住:客服mm知道顾客点菜的目的是为了让不同的厨师去做。再直接一些就是为了使用不同的命令。

    上面的吃饭问题和我们的遥控器问题差不多,都是包含下命令,命令的执行者,以及命令的具体内容。如果还是有些不清楚的话,就用简单的程序模拟一下上面的过程:

     1 class Program 
     2 { 
     3      static void Main(string[] args) 
     4      { 
     5          MM mm = new MM(); 
     6          //想吃热菜 
     7          mm.SetOrder(new ReCaiOrder()); 
     8          //mm还需要把菜单拿到厨师那里哦 
     9          mm.OnOrder(); 
    10          //想吃凉菜 
    11          mm.SetOrder(new LiangCaiOrder()); 
    12          mm.OnOrder(); 
    13          Console.ReadKey(); 
    14      } 
    15 }
    16 
    17 /// <summary> 
    18 /// 订单 
    19 /// </summary> 
    20 interface IOrder 
    21 { 
    22      void Excute(); 
    23 }
    24 
    25 /// <summary> 
    26 /// 凉菜做法 
    27 /// </summary> 
    28 class LiangCaiChuShi 
    29 { 
    30      public void MakeCook() 
    31      { 
    32          Console.WriteLine("凉菜~!!!"); 
    33      } 
    34 } 
    35 /// <summary> 
    36 /// 凉菜订单 
    37 /// </summary> 
    38 class LiangCaiOrder:IOrder 
    39 { 
    40      LiangCaiChuShi chushi=new LiangCaiChuShi(); 
    41      public void Excute() 
    42      { 
    43          chushi.MakeCook(); 
    44      } 
    45 } 
    46 /// <summary> 
    47 /// 热菜做法 
    48 /// </summary> 
    49 class ReCaiChuShi 
    50 { 
    51      public void Cook() 
    52      { 
    53          Console.WriteLine("热菜!!"); 
    54      } 
    55 }
    56 
    57 /// <summary> 
    58 /// 热菜订单 
    59 /// </summary> 
    60 class ReCaiOrder : IOrder 
    61 { 
    62      ReCaiChuShi chushi=new ReCaiChuShi(); 
    63      public void Excute() 
    64      { 
    65          chushi.Cook(); 
    66      } 
    67 } 
    68 class MM 
    69 { 
    70      IOrder order; 
    71      public void SetOrder(IOrder order) 
    72      { 
    73          this.order = order; 
    74      } 
    75      public void OnOrder() 
    76      { 
    77          order.Excute(); 
    78      } 
    79 } 
    View Code

    上面的例子中,厨师的做法有可能不同,就像我们遥控器的开关一样,电器的开关方法不一定相同。如果要执行,只需要把这个方法包在命令的激发方法中,然后去调用具体的方法就可以了。

    尽管上面的例子有些牵强,但是还是模拟了命令的过程,为我们遥控器的设计提供了思路。

    2.4解决问题

    回到我们的遥控器问题上面来,我们可以先定义好我们的风扇,白炽灯,热水器。然后定义其分别的开关命令,每个命令都有自己对应的电器引用,而且会在命令的Excute中包装电器的开或者关,最后需要把命令安装到遥控器上面,在遥控器上每个按钮都对应有自己的激发方法,其代码如下:

      1 using System;
      2 using System.Collections.Generic;
      3 using System.Linq;
      4 using System.Text;
      5 using System.Threading.Tasks;
      6 
      7 namespace RemoteControl
      8 {
      9     class Program
     10     {
     11         static void Main(string[] args)
     12         {
     13             //家中的电器
     14             Fan fan=new Fan();
     15             Light light=new Light();
     16             Heater heater=new Heater();
     17 
     18             //电器分别对应的命令
     19             FanOffCommand fanOffCommand=new FanOffCommand(fan);
     20             FanOnCommand fanOnCommand=new FanOnCommand(fan);
     21             LightOnCommand lightOnCommand=new LightOnCommand(light);
     22             LightOffCommand lightOffCommand=new LightOffCommand(light);
     23             HeaterOnCommand heaterOnCommand=new HeaterOnCommand(heater);
     24             HeaterOffCommand heaterOffCommand=new HeaterOffCommand(heater);
     25             RemoteControl remoteControl = new RemoteControl();
     26 
     27             //设置遥控器
     28             remoteControl.SetCommand(0, fanOnCommand, fanOffCommand);
     29             remoteControl.SetCommand(1, lightOnCommand, lightOffCommand);
     30             remoteControl.SetCommand(2, heaterOnCommand, heaterOffCommand);
     31             //分别测试遥控器的命令
     32             remoteControl.OnButtonWasPress(1);
     33             remoteControl.OffButtonWasPress(1);
     34             remoteControl.OnButtonWasPress(0);
     35             remoteControl.OffButtonWasPress(0);
     36             Console.ReadKey();
     37         }
     38     }
     39 
     40     /// <summary>
     41     /// 风扇类
     42     /// </summary>
     43    public class Fan
     44     {
     45         public void FanOn()
     46         {
     47             Console.WriteLine("风扇开了");
     48         }
     49         public void FanOff()
     50         {
     51             Console.WriteLine("风扇关了");
     52         }
     53     }
     54     /// <summary>
     55     /// 灯类
     56     /// </summary>
     57    public class Light
     58    {
     59        public void LightOn()
     60        {
     61            Console.WriteLine("灯亮了");
     62        }
     63        public void LightOff()
     64        {
     65            Console.WriteLine("灯灭了");
     66        }
     67    }
     68     /// <summary>
     69     /// 热水器类
     70     /// </summary>
     71    public class Heater
     72    {
     73        public void HeaterOn()
     74        {
     75            Console.WriteLine("加热中");
     76        }
     77        public void HeaterOff()
     78        {
     79            Console.WriteLine("停止加热");
     80        }
     81    }
     82 
     83     /// <summary>
     84     /// 命令接口
     85     /// </summary>
     86    public interface ICommand
     87    {
     88        void Excute();
     89    }
     90 
     91    public class FanOnCommand : ICommand
     92    {
     93        Fan fan;
     94        public FanOnCommand(Fan fan)
     95        {
     96            this.fan = fan;
     97        }
     98         public void Excute()
     99         {
    100             this.fan.FanOn();
    101         }
    102     }
    103 
    104    public class FanOffCommand : ICommand
    105    {
    106        Fan fan;
    107        public FanOffCommand(Fan fan)
    108        {
    109            this.fan = fan;
    110        }
    111         public void Excute()
    112         {
    113             this.fan.FanOff();
    114         }
    115    }
    116 
    117    public class LightOnCommand : ICommand
    118    {
    119        Light light;
    120        public LightOnCommand(Light light)
    121        {
    122            this.light = light; 
    123        }
    124        public void Excute()
    125        {
    126            light.LightOn();
    127        }
    128 
    129    }
    130 
    131    public class LightOffCommand : ICommand
    132    {
    133        Light light;
    134        public LightOffCommand(Light light)
    135        {
    136            this.light = light;
    137        }
    138        public void Excute()
    139        {
    140            this.light.LightOff();
    141        }
    142    }
    143 
    144    public class HeaterOnCommand : ICommand
    145    {
    146        Heater heater;
    147        public HeaterOnCommand(Heater heater)
    148        {
    149            this.heater = heater;
    150        }
    151        public void Excute()
    152        {
    153            this.heater.HeaterOn();
    154        }
    155    }
    156 
    157    public class HeaterOffCommand : ICommand
    158    { 
    159        Heater heater;
    160        public HeaterOffCommand(Heater heater)
    161        {
    162            this.heater = heater;
    163        }
    164        public void Excute()
    165        {
    166            this.heater.HeaterOff();
    167        }
    168    }
    169 
    170    public class NoCommand : ICommand
    171    {
    172        public void Excute()
    173        { }
    174    }
    175 
    176    public class RemoteControl
    177    {
    178       private ICommand[] onCommands;
    179       private ICommand[] offCommands;
    180       public RemoteControl()
    181       {
    182          ICommand noCommand=new NoCommand();
    183           onCommands = new ICommand[4];
    184           offCommands = new ICommand[4];
    185           for (int i = 0; i < 4; i++)
    186           {
    187               onCommands[i] = noCommand;
    188               offCommands[i] = noCommand;
    189           }
    190       }
    191 
    192       public void SetCommand(int slot, ICommand onCommand, ICommand offCommand)
    193       {
    194           onCommands[slot] = onCommand;
    195           offCommands[slot] = offCommand;
    196       }
    197       public void OnButtonWasPress(int slot)
    198       {
    199           onCommands[slot].Excute();
    200       }
    201       public void OffButtonWasPress(int slot)
    202       {
    203           offCommands[slot].Excute();
    204       }
    205      
    206    }
    207 }
    208     
    View Code

    这样基本上就实现了我们的现有的三种电器的遥控。需要注意的是,在开始初始化遥控器时,对每个命令初始化成了NoCommand,也就是什么都不执行。在命令的初始化经常使用,同时这也解决了我们的在扩展前什么都不做的难题。看了风扇,白炽灯,热水器的遥控实现,进一步的扩展任何的电器,相信都不是什么难事。但是还有个功能没有实现,就是撤销到上一步的操作,接下来我们就来实现撤销操作。

    2.5问题的补充说明

    撤销操作就想我们遥控中的返回一样。基本上就是灯亮着,突然按了一下关灯,然后再按一下返回键,灯就亮了。其他的电器同样的道理。下面先看一下灯的撤销原理,命令除了执行外还有一个撤销,所以我们需要先都命令的接口添加一个方法。

    /// <summary>
    /// 命令接口
    /// </summary>
    public interface ICommand
    {
        void Excute();
        void Undo();
    }

    对于开灯需要做的修改如下:

    public class LightOnCommand : ICommand
       {
           Light light;
           public LightOnCommand(Light light)
           {
               this.light = light;
           }
           public void Excute()
           {
               light.LightOn();
           }
           /// <summary>
           /// 调用命令的反命令
           /// </summary>
           public void Undo()
           {
               light.LightOff();
           }
       }

    其他命令同理,代码会在源码中一并给出。也就是每个命令都有自己的反命令,在Undo方法里面也就是调用反命令的Excute方法。每当按下一个按钮时,就去记录其命令的名称,如果按撤销的话,就执行命名的Undo方法。下面给出主要代码:

    public void OnButtonWasPressed(int slot)
       {
           onCommands[slot].Excute();
           backCommand=onCommands[slot];
       }
       public void OffButtonWasPressed(int slot)
       {
           offCommands[slot].Excute();
           backCommand = offCommands[slot];
       }
       public void BackButtonWasPressed()
       {
           backCommand.Undo();
       }

    以上是对遥控器对命令的撤销,需要注意两点1、通过记住命令执行之前的状态,然后去恢复到原来的状态。2、在每次执行之后要记住执行的那个命令。也即记住命令和记住状态。

    除了一次执行一个命令和撤销一个命令,当然还可以一次执行多个命令。下面给出主要代码:

    public class MutlipleCommand : ICommand
       {
           ICommand[] commands;
           ICommand[] backCommands;
           public MutlipleCommand(ICommand[] commands)
           {
               this.commands = commands;
               backCommands = new ICommand[commands.Length];
           }


           public void Excute()
           {
               for (int i = 0; i < commands.Length; i++)
                {
                  commands[i].Excute();
                  backCommands[i] = commands[i];
                }
              
           }

           public void Undo()
           {
               for (int i = 0; i < commands.Length; i++)
               {
                   backCommands[i].Undo();
               }
           }
       }

    三、命令模式类图

    image

    四、总结

    命令模式主要通过中介Command实现了发出命令者和命令的执行者,也即Invoke类和Receiver的松耦合。本文先给出了命令模式的定义,通过吃饭的例子给出了使用命令模式实现遥控器设计思路,最后还提到了撤销命令和一个命令实现多个命令的做法。

    源码

  • 相关阅读:
    分布式共识协议RAFT基本原理
    slave_net_timeout, MASTER_HEARTBEAT_PERIOD, MASTER_CONNECT_RETRY,以及 MASTER_RETRY_COUNT设置和查看
    单例模式
    截取字符串Java
    Hibernate+struts+JqueryAjax+jSON实现无刷新三级联动
    数组集合区别
    Android Activity生命周期(图文)!
    Activity的生命周期
    Android四大组件
    各种时间获取 .net
  • 原文地址:https://www.cnblogs.com/lzhp/p/3395320.html
Copyright © 2011-2022 走看看