JS與設計模式之------命令模式Command
先給個具體案例,如下: 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()方法一條一條執行這些命令,從而恢複系統在崩潰前所做的資料更新。