zoukankan      html  css  js  c++  java
  • 设计模式(六)(Command Pattern)命令模式

    • 定义
      • 将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
      • 结构图:
    • 命令模式的角色划分:
      • Reciever(命令的接收者):接收命令,并知道如何进行必要的工作,实现具体的处理。任何类都可以当接收者。
      • Invoker(命令调用者):命令调用者持有一个命令对象,并在某个时间点调用命令对象的Execute()方法,将请求付诸实行。
      • Command(命令接口):Command为所有命令声明了一个接口。调用命令对象的Execute()方法,就可以让接收者进行相应的动作。这个接口也具备一个UnDo()方法,为了回滚动作使用,也可以不定义该方法。
      • ConcreteCommand(命令):ConcreateCommand定义了动作与接收者之间的绑定关系。调用者Execute()就可以发出请求,然后由ConcreateCommand调用就接收者的一个或者多个动作。
    • 应用示例:
      • 我们知道灯可以开、关,如果我们用程序去演示等的开关可以这么写:
        •  1 public class Program
           2 {
           3     public static void Main(string[] args)
           4     {
           5     Light light=new Light("light0001");
           6     // light turn on.
           7     light.On();
           8     // light turn off.
           9     light.Off();
          10     }
          11 }
          12 
          13 public class Light
          14 {
          15     public string Name { get; set; }
          16 
          17     public Light(string name)
          18     {
          19         this.Name = name;
          20     }
          21 
          22     /// <summary>
          23     /// Turn on the light
          24     /// </summary>
          25     public void On()
          26     {
          27         Console.WriteLine("light:{0} is turn on.", this.Name);
          28     }
          29 
          30     /// <summary>
          31     /// Turn off the light
          32     /// </summary>
          33     public void Off()
          34     {
          35         Console.WriteLine("light:{0} is turn off.", this.Name);
          36     }
          37 }
      • 上边的示例把灯的开、关动作调用都直接写在了Main的代码块中。但针对该例子这样写似乎没有看出有什么不妥的,但是如果是FTP的服务端接收命令解析时,我们把接收到的100左右个命令,分别定义出对应的函数到一个类中,这时就会发现这样做耦合度太高了,维护起来就不像单独On(),Off()两个动作这么轻松。
      • 试想,如果把FTP服务器端,接收到的命令拆分成不同的“命令”,有单一的“命令调用者”,这样会不会把结构划分的更清晰?
      • 备注:FTP向服务端请求命令:LS、DIR、MLS、MDIR、MKDIR、RMDIR
        LS有点像UNIX下的LS(LIST)命令:
        DIR相当于LS-L(LIST-LONG);
        MLS只是将远端某目 录下的文件存于LOCAL端的某文件里;
        MDIR相当于MLS;
        MKDIR像DOS下的MD(创建子目录)一样:
        RMDIR像DOS下的RD(删除子目录)一样。
      • 下边我们将上边的例子Light按照Command设计模式来改写,看下效果:
        • 命令接口:Command
          1 public interface ICommand
          2 {
          3 
          4     void Execute();
          5 
          6 }
        • 命令的接收者:Reciever
          •  1 public class Light
             2 {
             3     public string Name { get; set; }
             4 
             5     public Light(string name)
             6     {
             7         this.Name = name;
             8     }
             9 
            10     /// <summary>
            11     /// Turn on the light
            12     /// </summary>
            13     public void On()
            14     {
            15         Console.WriteLine("light:{0} is turn on.", this.Name);
            16     }
            17 
            18     /// <summary>
            19     /// Turn off the light
            20     /// </summary>
            21     public void Off()
            22     {
            23         Console.WriteLine("light:{0} is turn off.", this.Name);
            24     }
            25 }
        • 命令的封装者:ConcreateCommand
           1 public class LightOnCommand : ICommand
           2 {
           3     private Light light;
           4 
           5     public LightOnCommand(Light light)
           6     {
           7         this.light = light;
           8     }
           9 
          10     public void Execute()
          11     {
          12         this.light.On();
          13     }
          14 }
        • 命令的封装者:ConcreateCommand
           1 public class LightOffCommand : ICommand
           2 {
           3     private Light light;
           4 
           5     public LightOffCommand(Light light)
           6     {
           7         this.light = light;
           8     }
           9 
          10     public void Execute()
          11     {
          12         this.light.Off();
          13     }
          14 }
        • 命令的封装者:命令的Invoker
           1 public class CommandControl
           2 {
           3     private ICommand command;
           4 
           5     public CommandControl() { }
           6 
           7     public void SetCommand(ICommand command)
           8     {
           9         this.command = command;
          10     }
          11 
          12     public void ButtonPresssed()
          13     {
          14         this.command.Execute();
          15     }
          16 }
        • 客户端:client
           1 class Program
           2 {
           3     static void Main(string[] args)
           4     {
           5         CommandControl control = new CommandControl();
           6         Light light = new Light("light_001");
           7         LightOnCommand lightOn = new LightOnCommand(light);
           8         LightOffCommand lightOff = new LightOffCommand(light);
           9 
          10         control.SetCommand(lightOn);
          11         control.ButtonPresssed();
          12 
          13         control.SetCommand(lightOff);
          14         control.ButtonPresssed();
          15 
          16         control.SetCommand(lightOn);
          17         control.ButtonPresssed();
          18 
          19         control.SetCommand(lightOff);
          20         control.ButtonPresssed();
          21 
          22         Console.ReadKey();
          23     }
          24 }
    • 开发中用到:
      •     
      • 怎么调用:
        •   
      • Command:
        •   
      • Reciever:
        •   
    • 应用场景:
      • 多级UnDo操作的应用场景;
        •   如何实现多层撤销操作?
        •   首先,不要只是记录最后一个被执行的命令,而使用一个堆栈记录操作过程的每一个命令。
        •   然后,不管什么时候操作了什么,都可以从堆栈中取出最上层的命令,然后调用它的UnDo()方法。
      •     命令可以将运算块打包(一个接收者和一组动作),让后将它传来传去,就像是一般的对象一样。现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。我们可以衍生一些应用,例如:日程安排(Scheduler)、线程池、工作队列等。
      • 日志请求:某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。通过两个方法 Store(),Load(),命令模式就能够支持这一点。
    • 总结&优缺点:
      • 命令模式的实际应用还是挺广泛的,在FTP服务器端Socket接收到客户端命令请求,每个命令都需要做出对应的操作,为了业务的明细化,我们习惯上把这样的请求参数化,例如:SupperSocket.FTP中就是一个很好的命令模式的应用。
      • 优点:把‘请求’作为参数传递,将调用、参数、动作分开,解耦,业务更加清晰,代码逻辑更加明晰化,职责也更单一;
      • 缺点:子类划分的更多了,没出现一个新的请求,都需要new更多的类

    参考资料:《Head First 设计模式》

    欢迎拍砖!请牛人们给指点。

  • 相关阅读:
    VS2019远程调试
    windows下使用redis-desktop-manager远程连接redis失败问题
    无法打开到SQL Server的连接 (Microsoft SQL Server, 错误:53) .
    由于管理员设置的策略,该磁盘处于脱机状态
    window下ping端口tcping
    dos命令远程登陆window server服务器并重启
    大二寒假作业之Android
    大二寒假作业之Android
    大二寒假作业之Android
    大二寒假作业之JavaWeb
  • 原文地址:https://www.cnblogs.com/yy3b2007com/p/4630802.html
Copyright © 2011-2022 走看看