大熊君说说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()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。

 

 

                哈哈哈,本篇结束,未完待续,希望和大家多多交流够沟通,共同进步(*^__^*) 呼呼呼……(*^__^*)                                                                

 

posted @ 2014-12-29 10:44  大熊君Bigbear  阅读(2887)  评论(17编辑  收藏  举报