设计模式(六)(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
- 我们知道灯可以开、关,如果我们用程序去演示等的开关可以这么写:
- 开发中用到:
- 怎么调用:
- Command:
- Reciever:
- 应用场景:
- 多级UnDo操作的应用场景;
- 如何实现多层撤销操作?
- 首先,不要只是记录最后一个被执行的命令,而使用一个堆栈记录操作过程的每一个命令。
- 然后,不管什么时候操作了什么,都可以从堆栈中取出最上层的命令,然后调用它的UnDo()方法。
- 命令可以将运算块打包(一个接收者和一组动作),让后将它传来传去,就像是一般的对象一样。现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。我们可以衍生一些应用,例如:日程安排(Scheduler)、线程池、工作队列等。
- 日志请求:某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。通过两个方法 Store(),Load(),命令模式就能够支持这一点。
- 多级UnDo操作的应用场景;
- 总结&优缺点:
- 命令模式的实际应用还是挺广泛的,在FTP服务器端Socket接收到客户端命令请求,每个命令都需要做出对应的操作,为了业务的明细化,我们习惯上把这样的请求参数化,例如:SupperSocket.FTP中就是一个很好的命令模式的应用。
- 优点:把‘请求’作为参数传递,将调用、参数、动作分开,解耦,业务更加清晰,代码逻辑更加明晰化,职责也更单一;
- 缺点:子类划分的更多了,没出现一个新的请求,都需要new更多的类。
参考资料:《Head First 设计模式》
欢迎拍砖!请牛人们给指点。
基础才是编程人员应该深入研究的问题,比如:
1)List/Set/Map内部组成原理|区别
2)mysql索引存储结构&如何调优/b-tree特点、计算复杂度及影响复杂度的因素。。。
3)JVM运行组成与原理及调优
4)Java类加载器运行原理
5)Java中GC过程原理|使用的回收算法原理
6)Redis中hash一致性实现及与hash其他区别
7)Java多线程、线程池开发、管理Lock与Synchroined区别
8)Spring IOC/AOP 原理;加载过程的。。。
【+加关注】。