大熊君说说JS与设计模式之------命令模式Command
一,总体概要
1,笔者浅谈
日常生活中,我们在看电视的时候,通过遥控器选择我们喜欢的频道时,此时我们就是客户端的角色,遥控器的按钮相当于客户请求,而具体执行的对象就是命令对象,
命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
先给个具体事例,如下:
1 function add(x, y) { return x + y; } ; 2 function sub(x, y) { return x - y; } ; 3 function mul(x, y) { return x * y; } ; 4 function div(x, y) { return x / y; } ; 5 6 var Command = function (execute, undo, value) { 7 this.execute = execute; 8 this.undo = undo; 9 this.value = value; 10 } 11 12 var AddCommand = function (value) { 13 return new Command(add, sub, value); 14 }; 15 16 var SubCommand = function (value) { 17 return new Command(sub, add, value); 18 }; 19 20 var MulCommand = function (value) { 21 return new Command(mul, div, value); 22 }; 23 24 var DivCommand = function (value) { 25 return new Command(div, mul, value); 26 }; 27 28 var Calculator = function () { 29 var current = 0; 30 var commands = []; 31 32 function action(command) { 33 var name = command.execute.toString().substr(9, 3); 34 return name.charAt(0).toUpperCase() + name.slice(1); 35 } 36 37 return { 38 execute: function (command) { 39 current = command.execute(current, command.value); 40 commands.push(command); 41 log.add(action(command) + ": " + command.value); 42 }, 43 44 undo: function () { 45 var command = commands.pop(); 46 current = command.undo(current, command.value); 47 log.add("Undo " + action(command) + ": " + command.value); 48 }, 49 50 getCurrentValue: function () { 51 return current; 52 } 53 } 54 } 55 56 var log = (function () { 57 var log = ""; 58 59 return { 60 add: function (msg) { log += msg + "\n"; }, 61 show: function () { alert(log); log = ""; } 62 } 63 })(); 64 65 function run() { 66 var calculator = new Calculator(); 67 calculator.execute(new AddCommand(100)); 68 calculator.execute(new SubCommand(24)); 69 calculator.execute(new MulCommand(6)); 70 calculator.execute(new DivCommand(2)); 71 calculator.undo(); 72 calculator.undo(); 73 log.add("\nValue: " + calculator.getCurrentValue()); 74 log.show(); 75 }
这是一个计算器的例子,将每一个具体操作以对象的形式进行封装,计算器接收到我们的请求后,
对发出具体的命令,是+,-,还是*/。这样,我们把请求传给计算器,计算器来具体执行需要哪些命令。
这样一来虽然结果是一样的,都是计算出结果,但是过程去截然不同喽。最大限度的降低了耦合。
二,源码案例参考
在命令模式的总体思路是,它给我们提供一种分开的任何执行命令发布命令的责任,这种责任的不同对象而不是授权。
简单的命令对象结合在一起的一种行为对象要调用动作。他们始终包括一个执行操作(如run()或execute())。所有的命令对象具有相同的接口,可以很容易地被交换的需要。
三,案例引入
具体的Command模式代码各式各样,因为如何封装命令,不同系统,有不同的做法。下面事例是将命令封装在一个List中,任何对象一旦加入List中,实际上装入了一个封闭的黑盒中,对象的特性消失了,只有取出时,才有可能模糊的分辨出:
典型的Command模式需要有一个接口,接口中有一个统一的方法,这就是"将命令/请求封装为对象"。
(1) ,建立程序猿实体类
1 function Programmer(){ 2 this.execute = function(){ 3 console.log("程序猿写代码!") ; 4 } ; 5 } ;
(2) ,建立工程师实体类
1 function Engineer(){ 2 this.execute = function(){ 3 console.log("工程师盖房子!") ; 4 } ; 5 } ;
(3) ,建立政治家实体类
1 function Politician(){ 2 this.execute = function(){ 3 console.log("政治家喷人!") ; 4 } ; 5 } ;
(4) ,建立黑盒子类
按照通常做法,我们就可以直接调用这三个Command,但是使用Command模式,我们要将他们封装起来,扔到黑盒子List里去:
1 function Producer(){ 2 var list = [] ; 3 return { 4 produceRequests : function(){ 5 list.push(new Engineer()) ; 6 list.push(new Programmer()) ; 7 list.push(new Politician()) ; 8 return list ; 9 } 10 } 11 }
这三个命令进入List中后,已经失去了其外表特征,以后再取出,也可能无法分辨出谁是Engineer,谁是Programmer了,看下面客户端如何调用Command模式:
(5) ,建立命令客户端类
1 function CMDClient(){ 2 var cmdlist = Producer.produceRequests() ; 3 for(var p in cmdlist){ 4 (cmdlist[p]).execute() ; 5 } 6 } ;
理解了上面的代码的核心原理,在使用中,就应该各人有自己方法了,特别是在如何分离调用者和具体命令上,有很多实现方法,上面的代码是使用"从List过一遍"的做法.这种做法只是为了演示.
使用Command模式的一个好理由还因为它能实现Undo功能.每个具体命令都可以记住它刚刚执行的动作,并且在需要时恢复.
四,总结一下
命令具有以下的优点:
(1)命令模式使新的命令很容易地被加入到系统里。
(2)允许接收请求的一方决定是否要否决请求。
(3)能较容易地设计一个命令队列。
(4)可以容易地实现对请求的撤销和恢复。
(5)在需要的情况下,可以较容易地将命令记入日志。
更松散的耦合
命令模式使得发起命令的对象——客户端,和具体实现命令的对象——接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。
更动态的控制
命令模式把请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,从而使得系统更灵活。
很自然的复合命令
命令模式中的命令对象能够很容易地组合成复合命令,也就是宏命令,从而使系统操作更简单,功能更强大。
更好的扩展性
由于发起命令的对象和具体的实现完全解耦,因此扩展新的命令就很容易,只需要实现新的命令对象,然后在装配的时候,把具体的实现对象设置到命令对象中,然后就可以使用这个命令对象,已有的实现完全不用变化。
应用场景
1)使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。
2)需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
3)系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
4)如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。
哈哈哈,本篇结束,未完待续,希望和大家多多交流够沟通,共同进步(*^__^*) 呼呼呼……(*^__^*)