當(dāng)前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
javascript设计模式--命令模式
生活随笔
收集整理的這篇文章主要介紹了
javascript设计模式--命令模式
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
1 <!DOCTYPE html>
2 <html>
3 <head>
4 <title>命令模式</title>
5 <meta charset="utf-8">
6 </head>
7 <body>
8
9 <script>
10 /**
11 * 命令模式
12 *
13 * 定義:
14 * 將一個請求封裝為一個對象,從而使你可用不同的請求對客戶進行參數(shù)化,對請求排隊或記錄請求日志,以及支持可撤銷的操作。
15 *
16 * 本質(zhì):
17 * 封裝請求
18 *
19 * 命令模式是一種封裝方法調(diào)用的方式。命令模式與普通函數(shù)所有不同。它可以用來對方法調(diào)用進行參數(shù)化處理和傳送,經(jīng)這樣處理過的方法調(diào)用可以在任何需要的時候執(zhí)行。它也可以用來消除調(diào)用操作的對象和實現(xiàn)操作對象之間的耦合,這位各種具體的類的更換帶來了極大的靈活性。這種模式可以用在許多不同場合,不過它在創(chuàng)建用戶界面這一方面非常有用,特別是在需要不受限的(unlimited)取消(undo)操作的時候,它還可以用來替代回調(diào)函數(shù),因為它能夠提高在對象之間傳遞的操作的模塊化程度。
20 *
21 * 在命令模式中,會定義一個命令的接口,用來約束所有的命令對象,然后提供具體的命令實現(xiàn),每個命令實現(xiàn)對象是對客戶端某個請求的封裝,對應(yīng)于機箱上的按鈕,一個機箱上可以有很多按鈕,也就相當(dāng)于會有多個具體的命令實現(xiàn)對象。
22 * 在命令模式中,命令對象并不知道如何處理命令,會有相應(yīng)的接收者對象來真正執(zhí)行命令。就像電腦的例子,機箱上的按鈕并不知道如何處理功能,而是把這個請求轉(zhuǎn)發(fā)給主板,由主辦來執(zhí)行真正的功能,這個主板就相當(dāng)于命令模式的接收者。
23 * 在命令模式中,命令對象和接收者對象的關(guān)系,并不是與生俱來的,需要有一個裝配的過程,命令模式中的Client對象可以實現(xiàn)這樣的功能。這就相當(dāng)于在電腦的例子中,有了機箱上的按鈕,也有了主板,還需要一個連接線把這個按鈕連接到主板上才行。
24 * 命令模式還會提供一個Invoker對象來持有命令對象。就像電腦的例子,機箱上會有多個按鈕,這個機箱就相當(dāng)于命令模式的Invoker對象。這樣一來,命令模式的客戶端就可以通過Invoker來觸發(fā)并要求執(zhí)行相應(yīng)的命令了,這也相當(dāng)于真正的客戶是按下機箱上的按鈕來操作電腦一樣。
25 *
26 * 命令模式的關(guān)鍵
27 * 命令模式的關(guān)鍵之處就是把請求封裝成對象,也就是命令對象,并定義了統(tǒng)一的執(zhí)行操作的接口,這個命令對象可以被存儲,轉(zhuǎn)發(fā),記錄,處理,撤銷等,整個命令模式都是圍繞這個對象在進行。
28 *
29 * 命令模式的組裝和調(diào)用
30 * 在命令模式中經(jīng)常會有一個命令的組裝者,用它來維護命令的“虛”實現(xiàn)和真實實現(xiàn)之間的關(guān)系。如果是超級智能的命令,也就是說命令對象自己完全實現(xiàn)好了,不需要接收者,那就是命令模式的退化,不需要接受者,自然也不需要組裝者了。
31 * 而真正的用戶就是具體化請求的內(nèi)容,然后提交請求進行觸發(fā)就可以了。真正的用戶會通過Invoker來觸發(fā)命令。
32 * 在實際開發(fā)過程中,Client和Invoker可以融合在一起,由客戶在使用命令模式的時候,先進行命令對象和接收者的組裝,組裝完成后,就可以調(diào)用命令執(zhí)行請求。
33 *
34 * 命令模式的接收者
35 * 接收者可以是任意的類,對它沒有什么特殊要求,這個對象知道如何真正執(zhí)行命令的操作,執(zhí)行時是從Command的實現(xiàn)類里面轉(zhuǎn)調(diào)過來。
36 * 一個接收者對象可以處理多個命令,接收者和命令之間沒有約定的對應(yīng)關(guān)系。接收者提供的方法個數(shù),名稱,功能和命令中的可以不一樣,只要能夠通過調(diào)用接收者的方法來實現(xiàn)命令對應(yīng)的功能就可以了。
37 *
38 * 命令模式的調(diào)用順序
39 * 使用命令模式的過程分成兩個階段,一個階段是組裝對象和接收者對象的過程,另外一個階段是觸發(fā)調(diào)用Invoker,來讓命令真正的執(zhí)行。
40 * 組裝過程:
41 * 1.創(chuàng)建接收者對象。
42 * 2.創(chuàng)建命令對象,設(shè)置命令對象和接收者對象的關(guān)系。
43 * 3.創(chuàng)建Invoker對象。
44 * 4.把命令對象設(shè)置到Invoker中,讓Invoker持有命令對象。
45 * 執(zhí)行過程:
46 * 1.調(diào)用Invoker的方法,觸發(fā)要求執(zhí)行命令。
47 * 2.要求持有的命令對象只能執(zhí)行功能。
48 * 3.要求持有的接收者真正實現(xiàn)功能。
49 *
50 */
51
52 (function () {
53 // 示例代碼
54
55 /**
56 * 具體的命令實現(xiàn)對象
57 * @params {Object} receiver 持有相應(yīng)的接收者對象
58 */
59 function Command(receiver) {
60 this.receiver = receiver;
61 // 命令對象可以有自己的狀態(tài)
62 this.state = '';
63 }
64
65 Command.prototype.execute = function () {
66 // 通常會轉(zhuǎn)調(diào)接收者對象的相應(yīng)方法,讓接收者來真正執(zhí)行功能
67 this.receiver.action();
68 };
69
70 // 接收者對象
71 function Receiver() {
72 }
73
74 // 真正執(zhí)行命令相應(yīng)地操作
75 Receiver.prototype.action = function () {
76 };
77
78 /**
79 * 調(diào)用者
80 *
81 */
82 function Invoker() {
83 }
84
85 /**
86 * @params {Object} command 持有命令對象
87 */
88 Invoker.prototype.setCommand = function (command) {
89 this.command = command;
90 };
91 // 要求命令執(zhí)行請求
92 Invoker.prototype.runCommand = function () {
93 this.command.execute();
94 };
95
96 new function Client() {
97 var receiver = new Receiver();
98 var command = new Command(receiver);
99 var invoker = new Invoker();
100 invoker.setCommand(command);
101 invoker.runCommand();
102 }();
103 }());
104
105 /*
106 命令的結(jié)構(gòu)
107
108 最簡形式的命令對象是一個操作和用以調(diào)用這個操作的對象的結(jié)合體。所有的命令對象都有一個執(zhí)行操作(execute operation),其用途就是調(diào)用命令對象所綁定的操作。在大多數(shù)命令對象中,這個操作是一個名為execute或run的方法。使用同樣接口的所有命令對象都可以被同等對待,并且可以隨意互換,這是命令模式的魅力之一。
109
110 假設(shè)你想設(shè)計一個網(wǎng)頁,客戶可以在上面執(zhí)行一些與自己的賬戶相關(guān)的操作,比如啟用和停用某些廣告。因為不知道其中的具體廣告數(shù)量,所以你想設(shè)計一個盡可能靈活的用戶界面(UI)。為此你打算用命令模式來弱化按鈕之類的用戶界面元素與其操作之間的耦合。
111 */
112
113 // 定義兩個類,分別用來封裝廣告的start方法和stop方法
114 // StopAd command class
115 var StopAd = function (adObject) {
116 this.ad = adObject;
117 };
118 StopAd.prototype.execute = function () {
119 this.ad.stop();
120 };
121
122 // StartAd command class
123 var StartAd = function (adObject) {
124 this.ad = adObject;
125 };
126 StartAd.prototype.execute = function () {
127 this.ad.start();
128 };
129 /*
130 現(xiàn)在有個兩個可用在用戶界面中的類,它們具有相同的接口。你不知道也不關(guān)心adObject的具體實現(xiàn)細節(jié),只要它實現(xiàn)了start和stop方法就行。借助于命令模式,可以實現(xiàn)用戶界面對象與廣告對象的隔離。
131 */
132
133 /*
134 下面的代碼創(chuàng)建的用戶界面中,用戶名下的每個廣告都有兩個按鈕,分別用于啟動和停止廣告的輪播:
135 */
136 // implementation code
137 var ads = getAds();
138 for (var i = 0, len = ads.length; i < len; i++) {
139 // Create command objects for starting and stopping the ad
140 var startCommand = new StartAd(ads[i]);
141 var stopCommand = new StopAd(ads[i]);
142
143 // Create the UI elements that will execute the command on click
144 new UIButton('Start ' + ads[i].name, startCommand);
145 new UIButton('stop ' + ads[i].name, stopCommand);
146 }
147 /*
148 UIButton類的構(gòu)造函數(shù)有兩個參數(shù),一個是按鈕上的文字,另一個是命令對象。它會在網(wǎng)頁上生成一個按鈕,該按鈕被點擊時會執(zhí)行那個命令對象的execute方法。這個類也不需要知道所用命令對象的確切實現(xiàn)。因為所有命令對象都實現(xiàn)了execute方法,所以可以把任何一種命令對象提供給UIButton。這有助于創(chuàng)建高度模塊化和低耦合的用戶界面。
149 */
150
151 /*
152 用閉包創(chuàng)建命令對象
153
154 還有另外一種辦法可以用來封裝函數(shù)。這種辦法不需要創(chuàng)建一個具有execute方法的對象,而是把想要執(zhí)行的方法包裝在閉包中。如果想要創(chuàng)建的命令對象像前例中那樣只有一個方法。那么這種辦法由其方便。現(xiàn)在你不再調(diào)用execute方法,因為那個命令可以作為函數(shù)直接執(zhí)行。這樣做還可以省卻作用域和this關(guān)鍵字的綁定的煩惱。
155 */
156
157 // Command using closures
158 function makeSart(adObject) {
159 return function () {
160 adObject.start();
161 };
162 }
163 function makeStop(adObject) {
164 return function () {
165 adObject.stop();
166 };
167 }
168
169 // Implementation code
170 var startCommand = makeStart(ads[0]);
171 var stopCommand = makeStop(ads[0]);
172
173 startCommand();
174 stopCommand();
175 /*
176 不適用于需要多個命令方法的場合,比如后面要實現(xiàn)取消功能的示例
177 */
178
179 /*
180 客戶,調(diào)用者和接收者
181
182 這個系統(tǒng)中有三個參與者:客戶(client),調(diào)用者(invoking object)和接收者(receiving object)。客戶負責(zé)實例化命令并將其交給調(diào)用者。在前面的例子中,for循環(huán)中的代碼就是客戶。它通常被包裝為一個對象,但也不是非這樣不可。調(diào)用者接過命令并將其保存下來。它會在某個時候調(diào)用該命令對象的execute方法,或者將其交給另一個潛在的調(diào)用者。前例中的調(diào)用者就是UIButton類創(chuàng)建的按鈕。用戶點擊它的時候,它就會調(diào)用命令對象的execute方法。接收者則是實際執(zhí)行操作的對象。調(diào)用者進行“commandObject.execute()”這種形式的調(diào)用時,它所調(diào)用的方法將轉(zhuǎn)而以“receiver.action()”這種形式調(diào)用恰當(dāng)?shù)姆椒ā6邮照呔褪菑V告對象,它所能執(zhí)行的操作要么是start方法,要么是stop方法。
183
184 客戶創(chuàng)建命令,調(diào)用者執(zhí)行該命令,接收者在命令執(zhí)行時執(zhí)行相應(yīng)操作。
185 所有使用命令模式的系統(tǒng)都有客戶和調(diào)用者,但不一定有接收者。
186 */
187
188 // 在命令模式中使用接口
189 // If no exception is thrown, youcan safely invoke the
190 // execute operation
191 someCommand.execute();
192
193 // 如果用必報來創(chuàng)建命令函數(shù),只需檢查是否為函數(shù)即可
194 if (typeof someCommand !== 'function') {
195 throw new Error('Command isn\'t a function');
196 }
197
198
199 // 命令對象的類型
200 /*
201 簡單命令對象就是把現(xiàn)有接收者的操作(廣告對象的start和stop方法)與調(diào)用者(按鈕)綁定在一起。這類命令對象最簡單,其模塊程度也最高。它們與客戶,接收者和調(diào)用者之間只是松散地偶合在一起:
202 */
203 // SimpleCommand, a loosely coupled, simple command class.
204 var SimpleCommand = function (receiver) {
205 this.receiver = receiver;
206 };
207 SimpleCommand.prototype.execute = function () {
208 this.receiver.action();
209 };
210
211 /*
212 另一種則是那種封裝著一套復(fù)雜指令的命令對象。這種命令對象實際上沒有接受者,因為它自己提供了操作的具體實現(xiàn)。它并不把操作委托給接收者實現(xiàn),所有用于實現(xiàn)相關(guān)操作的代碼都包含在其內(nèi)部:
213 */
214 // ComplexCommand, a tightly coupled, complex command class.
215 var ComplexCommand = function () {
216 this.logger = new Logger();
217 this.xhrHandler = XhrManager.createXhrHandler();
218 this.parameters = {};
219 };
220 ComplexCommand.prototype = {
221 setParameter: function (key, value) {
222 this.parameters[key] = value;
223 },
224 execute: function () {
225 this.logger.log('Executing command');
226 var postArray = [];
227 for (var key in this.parameters) {
228 if (this.parameters.hasOwnProperty(key)) {
229 postArray.push(key + '=' + this.parameters[key]);
230 }
231 }
232 var postString = postArray.join('&');
233 this.xhrHandler.request(
234 'POST',
235 'script.php',
236 function () {
237 },
238 postString
239 );
240 }
241 };
242
243 /*
244 有些命令對象不但封裝了接收者的操作,而且其execute方法中也具有一些實現(xiàn)代碼。這類命令對象是一個灰色地帶:
245 */
246 // GreyAreaCommand, somewhere between simple and complex
247 var GreyAreaCommand = function (receiver) {
248 this.logger = new Logger();
249 this.receiver = receiver;
250 };
251 GreyAreaCommand.prototype.execute = function () {
252 this.logger.log('Executing command');
253 this.receiver.prepareAction();
254 this.receiver.action();
255 };
256
257 /*
258 簡單命令對象一般用來消除兩個對象(接受著和調(diào)用者)之間的耦合,而復(fù)雜命令對象則一般用來封裝不可分的或事務(wù)性的指令。
259 */
260
261 // 實例: 菜單項
262 // 菜單組合對象
263 /*
264 接下來要實現(xiàn)的事Menubar,Menu和MenuItem類,作為一個整體,他們要能顯示所有可用操作,并且根據(jù)要求調(diào)用這些操作,Menubar和Menu都是組合對象類,而MenuItem則是葉類。Menubar類保存著所有Menu實例:
265 */
266 // MenuBar class, a composite
267 var MenuBar = function () {
268 this.menus = {};
269 this.element = document.createElement('ul');
270 this.element.style.display = 'none';
271 };
272 MenuBar.prototype = {
273 add: function (menuObject) {
274 this.menus[menuObject.name] = menuObject;
275 this.element.appendChild(this.menus[menuObject.name].getElement());
276 },
277 remove: function (name) {
278 delete this.menus[name];
279 },
280 getChild: function (name) {
281 return this.menus[name];
282 },
283 getElement: function () {
284 return this.element;
285 },
286 show: function () {
287 this.element.style.display = '';
288 for (var name in this.menus) {
289 this.menus[name].show();
290 }
291 }
292 };
293
294 // Menu class, a composite
295 var Menu = function (name) {
296 this.name = name;
297 this.items = {};
298 this.element = document.createElement('li');
299 this.element.style.display = 'none';
300 this.container = document.createElement('ul');
301 this.element.appendChild(this.container);
302 };
303 Menu.prototype = {
304 add: function (menuItemObject) {
305 this.items[menuItemObject.name] = menuItemObject;
306 this.container.appendChild(this.items[menuItemObject.name].getElement());
307 },
308 remove: function () {
309 delete this.items[name];
310 },
311 getChild: function (name) {
312 return this.items[name];
313 },
314 getElement: function () {
315 return this.element;
316 },
317 show: function () {
318 this.element.style.display = '';
319 for (var name in this.items) {
320 this.items[name].show();
321 }
322 }
323 };
324
325 // 調(diào)用者類
326 // MenuItem class, a leaf
327 var MenuItem = function (name, command) {
328 this.name = name;
329 this.element = document.createElement('li');
330 this.element.style.display = 'none';
331 this.anchor = document.createElement('a');
332 this.anchor.href = '#';
333 this.element.appendChild(this.anchor);
334 this.anchor.innerHTML = this.name;
335
336 addEvent(this.anchor, 'click', function (e) {
337 e = e || window.event;
338 if (typeof e.preventDefault === 'function') {
339 e.preventDefault();
340 } else {
341 e.returnValue = false;
342 }
343 command.execute();
344 });
345 };
346 MenuItem.prototype = {
347 add: function () {
348 },
349 remove: function () {
350 },
351 getChild: function () {
352 },
353 getElement: function () {
354 return this.element;
355 },
356 show: function () {
357 this.element.style.display = '';
358 }
359 };
360
361 // 命令類
362 // MenuCommand class, a command object
363 var MenuCommand = function (action) {
364 this.action = action;
365 };
366 MenuCommand.prototype.execute = function () {
367 this.action.action();
368 };
369
370
371 // Receiver objects, instantiated from existing classes
372 var Test1 = function () {
373 console.log('test1');
374 };
375 Test1.prototype = {
376 action: function () {
377 console.log('this is test1 fn1');
378 }
379 };
380 var Test2 = function () {
381 console.log('test2');
382 };
383 Test2.prototype = {
384 action: function () {
385 console.log('this is test2 fn1');
386 }
387 };
388 var Test3 = function () {
389 console.log('test3');
390 };
391 var test1 = new Test1();
392 var test2 = new Test2();
393 var test3 = new Test3();
394
395 // Create the menu bar
396 var appMenuBar = new MenuBar();
397
398 // The File menu
399 var fileMenu = new Menu('File');
400
401 var test1Command1 = new MenuCommand(test1);
402
403 fileMenu.add(new MenuItem('test1-1', test1Command1));
404
405 appMenuBar.add(fileMenu);
406
407 var insertMenu = new Menu('Insert');
408 var test2Command2 = new MenuCommand(test2);
409 insertMenu.add(new MenuItem('test2-1', test2Command2));
410
411 appMenuBar.add(insertMenu);
412
413 document.body.appendChild(appMenuBar.getElement());
414 appMenuBar.show();
415
416
417 (function () {
418 // 補償式或者反操作式
419
420 // 取消操作和命令日志
421
422 // ReversibleCommand interface
423 var ReversibleCommand = new Interface('ReversibleCommand', ['execute', 'undo']);
424
425 // 接下來要做的是創(chuàng)建4個命令類,
426 // 它們分別用來向上下左右四個方向移動指針:
427 var MoveUp = function (cursor) {
428 this.cursor = cursor;
429 };
430 MoveUp.prototype = {
431 execute: function () {
432 this.cursor.move(0, -10);
433 },
434 undo: function () {
435 this.cursor.move(0, 10);
436 }
437 };
438
439 var MoveDown = function (cursor) {
440 this.cursor = cursor;
441 };
442 MoveDown.prototype = {
443 execute: function () {
444 this.cursor.move(0, 10);
445 },
446 undo: function () {
447 this.cursor.move(0, -10);
448 }
449 };
450
451 var MoveLeft = function (cursor) {
452 this.cursor = cursor;
453 };
454 MoveLeft.prototype = {
455 execute: function () {
456 this.cursor.move(-10, 0);
457 },
458 undo: function () {
459 this.cursor.move(10, 0);
460 }
461 };
462
463 var MoveRight = function (cursor) {
464 this.cursor = cursor;
465 };
466 MoveRight.prototype = {
467 execute: function () {
468 this.cursor.move(10, 0);
469 },
470 undo: function () {
471 this.cursor.move(-10, 0);
472 }
473 };
474
475 // 接收者,負責(zé)實現(xiàn)指針移動
476 // Cursor class 實現(xiàn)了命令類所要求的操作
477 var Cursor = function (width, height, parent) {
478 this.width = width;
479 this.height = height;
480 this.position = {
481 x: width / 2,
482 y: height / 2
483 };
484
485 this.canvas = document.createElement('canvas');
486 this.canvas.width = this.width;
487 this.canvas.height = this.height;
488 parent.appendChild(this.canvas);
489
490 this.ctx = this.canvas.getContext('2d');
491 this.ctx.fillStyle = '#cc0000';
492 this.move(0, 0);
493 };
494 Cursor.prototype.move = function (x, y) {
495 this.position.x += x;
496 this.position.y += y;
497
498 this.ctx.clearRect(0, 0, this.width, this.height);
499 this.ctx.fillRect(this.position.x, this.position.y, 3, 3);
500 };
501
502 // 下面這個裝飾者的作用就是在執(zhí)行一個命令之前先將其壓棧
503 // UndoDecorator class
504 var UndoDecorator = function (command, undoStack) {
505 this.command = command;
506 this.undoStack = undoStack;
507 };
508 UndoDecorator.prototype = {
509 execute: function () {
510 this.undoStack.push(this.command);
511 this.command.execute();
512 },
513 undo: function () {
514 this.command.undo();
515 }
516 };
517
518 // 用戶界面類,負責(zé)生成必要的HTML元素,并且為其注冊click事件監(jiān)聽器,
519 // 這些監(jiān)聽器要么調(diào)用execute方法要么調(diào)用undo方法:
520 // CommandButton class
521 var CommandButton = function (label, command, parent) {
522 this.element = document.createElement('button');
523 this.element.innerHTML = label;
524 parent.appendChild(this.element);
525
526 addEvent(this.element, 'click', function () {
527 command.execute();
528 });
529 };
530
531 // UndoButton class
532 var UndoButton = function (label, parent, undoStack) {
533 this.element = document.createElement('button');
534 this.element.innerHTML = label;
535 parent.appendChild(this.element);
536
537 addEvent(this.element, 'click', function () {
538 if (undoStack.length === 0) return;
539 var lastCommand = undoStack.pop();
540 lastCommand.undo();
541 });
542 };
543 /*
544 像UndoDecorator類一樣,UndoButton類的構(gòu)造函數(shù)也需要把命令棧作為參數(shù)傳入。這個棧其實就是一個數(shù)組。調(diào)用經(jīng)UndoDecorator對象裝飾過的命令對象的execute方法時這個命令對象會被壓入棧。為了執(zhí)行取消操作,取消按鈕會從命令棧中彈出最近的命令并調(diào)用其undo方法。這將逆轉(zhuǎn)剛執(zhí)行過的操作。
545 */
546
547 // Implementation code
548 var body = document.body;
549 var cursor = new Cursor(400, 400, body);
550 var undoStack = [];
551
552 var upCommand = new UndoDecorator(new MoveUp(cursor), undoStack);
553 var downCommand = new UndoDecorator(new MoveDown(cursor), undoStack);
554 var leftCommand = new UndoDecorator(new MoveLeft(cursor), undoStack);
555 var rightCommand = new UndoDecorator(new MoveRight(cursor), undoStack);
556
557 var upButton = new CommandButton('Up', upCommand, body);
558 var downButton = new CommandButton('Down', downCommand, body);
559 var leftButton = new CommandButton('Left', leftCommand, body);
560 var rightButton = new CommandButton('Right', rightCommand, body);
561 var undoButton = new UndoButton('Undo', body, undoStack);
562 }());
563
564
565 (function () {
566 // 使用命令日志實現(xiàn)不可逆操作的取消
567 /*
568 在畫布上畫線很容易,不過要取消這條線的繪制是不可能的。從一個點到另一個點的移動這種操作具有精確的對立操作,執(zhí)行后者的結(jié)果看起來就像前者被逆轉(zhuǎn)了一樣。但是對于從A到B畫一條線這種操作,從B到A再畫一條線是無法逆轉(zhuǎn)前一操作的,這只不過是在第一條線的上方又畫一條線而已。
569
570 取消這種操作的唯一辦法是清除狀態(tài),然后把之前執(zhí)行過的操作(不含最近那個)一次重做一遍。這很容易辦到,為此需要把所有執(zhí)行過的命令記錄在棧中。要想取消一個操作,需要做的就是從棧中彈出最近那個命令并棄之不用,然后清理畫布并從頭開始重新執(zhí)行記錄下來的所有命令。
571 */
572
573 // Movement commands
574 var MoveUp = function (cursor) {
575 this.cursor = cursor;
576 };
577 MoveUp.prototype = {
578 execute: function () {
579 this.cursor.move(0, -10);
580 }
581 };
582
583 var MoveDown = function (cursor) {
584 this.cursor = cursor;
585 };
586 MoveDown.prototype = {
587 execute: function () {
588 this.cursor.move(0, 10);
589 }
590 };
591
592 var MoveLeft = function (cursor) {
593 this.cursor = cursor;
594 };
595 MoveLeft.prototype = {
596 execute: function () {
597 this.cursor.move(-10, 0);
598 }
599 };
600
601 var MoveRight = function (cursor) {
602 this.cursor = cursor;
603 };
604 MoveRight.prototype = {
605 execute: function () {
606 this.cursor.move(10, 0);
607 }
608 };
609
610 // Cursor class, with an internal command stack
611 var Cursor = function (width, height, parent) {
612 this.width = width;
613 this.height = height;
614 this.commandStack = [];
615
616 this.canvas = document.createElement('canvas');
617 this.canvas.width = this.width;
618 this.canvas.height = this.height;
619 parent.appendChild(this.canvas);
620
621 this.ctx = this.canvas.getContext('2d');
622 this.ctx.strokeStyle = '#cc0000';
623 this.move(0, 0);
624 };
625 Cursor.prototype = {
626 move: function (x, y) {
627 var that = this;
628 this.commandStack.push(function () {
629 that.lineTo(x, y);
630 });
631 this.executeCommands();
632 },
633 lineTo: function (x, y) {
634 this.position.x += x;
635 this.position.y += y;
636 this.ctx.lineTo(this.position.x, this.position.y);
637 },
638 executeCommands: function () {
639 this.position = {
640 x: this.width / 2,
641 y: this.height / 2
642 };
643 this.ctx.clearRect(0, 0, this.width, this.height);
644 this.ctx.beginPath();
645 this.ctx.moveTo(this.position.x, this.position.y);
646 for (var i = 0, len = this.commandStack.length; i < len; i++) {
647 this.commandStack[i]();
648 }
649 this.ctx.stroke();
650 },
651 undo: function () {
652 this.commandStack.pop();
653 this.executeCommands();
654 }
655 };
656
657 // UndoButton class
658 var UndoButton = function (label, parent, cursor) {
659 this.element = document.createElement('button');
660 this.element.innerHTML = label;
661 parent.appendChild(this.element);
662 addEvent(this.element, 'click', function () {
663 cursor.undo();
664 });
665 };
666 // CommandButton class
667 var CommandButton = function (label, command, parent) {
668 this.element = document.createElement('button');
669 this.element.innerHTML = label;
670 parent.appendChild(this.element);
671
672 addEvent(this.element, 'click', function () {
673 command.execute();
674 });
675 };
676
677 var body = document.body;
678 var cursor = new Cursor(400, 400, body);
679
680 var upCommand = new MoveUp(cursor);
681 var downCommand = new MoveDown(cursor);
682 var leftCommand = new MoveLeft(cursor);
683 var rightCommand = new MoveRight(cursor);
684
685 var upButton = new CommandButton('Up', upCommand, body);
686 var downButton = new CommandButton('Down', downCommand, body);
687 var leftButton = new CommandButton('Left', leftCommand, body);
688 var rightButton = new CommandButton('Right', rightCommand, body);
689 var undoButton = new UndoButton('Undo', body, cursor);
690 }());
691
692 (function () {
693 // 宏命令
694 /*
695 去飯店吃飯過程。
696
697 客戶: 只負責(zé)發(fā)出命令,就是點菜操作。
698 命令對象: 就是點的菜。
699 服務(wù)員: 知道真正的接收者是誰,同時持有菜單,當(dāng)你點菜完畢,服務(wù)員就啟動命令執(zhí)行。
700 后廚, 涼菜部: 相當(dāng)于接收者。
701
702 菜單命令包含多個命令對象
703 */
704
705 // 坐熱菜的廚師
706 var HotCook = function () {
707 };
708 HotCook.prototype = {
709 cook: function (name) {
710 console.log('本廚師正在做:' + name);
711 }
712 };
713
714 // 做涼菜的廚師
715 var CoolCook = function () {
716 };
717 CoolCook.prototype = {
718 cook: function (name) {
719 console.log('涼菜' + name + '已經(jīng)做好,本廚師正在裝盤。');
720 }
721 }
722
723 // 定義了三道菜,每道菜是一個命令對象
724
725 var DuckCommand = function () {
726 this.cookApi = null;
727 };
728 DuckCommand.prototype = {
729 constructor: DuckCommand,
730 setCookApi: function (cookApi) {
731 this.cookApi = cookApi;
732 },
733 execute: function () {
734 this.cookApi.cook('北京烤鴨');
735 }
736 };
737
738 var ChopCommand = function () {
739 this.cookApi = null;
740 };
741 ChopCommand.prototype = {
742 constructor: ChopCommand,
743 setCookApi: function (cookApi) {
744 this.cookApi = cookApi;
745 },
746 execute: function () {
747 this.cookApi.cook('綠豆排骨煲');
748 }
749 };
750
751 var PorkCommand = function () {
752 this.cookApi = null;
753 };
754 PorkCommand.prototype = {
755 constructor: PorkCommand,
756 setCookApi: function (cookApi) {
757 this.cookApi = cookApi;
758 },
759 execute: function () {
760 this.cookApi.cook('蒜泥白肉');
761 }
762 };
763
764 // 菜單對象,宏命令對象
765 var MenuCommand = function () {
766 var col = [];
767
768 this.addCommand = function (cmd) {
769 col.push(cmd);
770 };
771
772 this.execute = function () {
773 for (var i = 0 , len = col.length; i < len; i++) {
774 col[i].execute();
775 }
776 };
777 };
778
779 // 服務(wù)員,負責(zé)組合菜單,負責(zé)組裝每個菜和具體的實現(xiàn)者。
780 var Waiter = function () {
781 var menuCommand = new MenuCommand();
782
783 // 客戶點菜
784 this.orderDish = function (cmd) {
785 var hotCook = new HotCook();
786 var coolCook = new CoolCook();
787
788 if (cmd instanceof DuckCommand) {
789 cmd.setCookApi(hotCook);
790 } else if (cmd instanceof ChopCommand) {
791 cmd.setCookApi(hotCook);
792 } else if (cmd instanceof PorkCommand) {
793 cmd.setCookApi(coolCook);
794 }
795
796 menuCommand.addCommand(cmd);
797 };
798
799 // 點菜完畢
800 this.orderOver = function () {
801 menuCommand.execute();
802 };
803 };
804
805 var waiter = new Waiter();
806 var chop = new ChopCommand();
807 var duck = new DuckCommand();
808 var pork = new PorkCommand();
809
810 waiter.orderDish(chop);
811 waiter.orderDish(duck);
812 waiter.orderDish(pork);
813
814 waiter.orderOver();
815
816 }());
817
818 (function () {
819 // 隊列請求
820
821 function createCommand(name) {
822 function Command(tableNum) {
823 this.cookApi = null;
824 this.tableNum = tableNum;
825 }
826
827 Command.prototype = {
828 setCookApi: function (cookApi) {
829 this.cookApi = cookApi;
830 },
831 execute: function () {
832 this.cookApi.cook(this.tableNum, name);
833 }
834 };
835
836 return Command;
837 }
838
839 var ChopCommand = createCommand('綠豆排骨煲');
840 var DuckCommand = createCommand('北京烤鴨');
841
842 var CommandQueue = {
843 cmds: [],
844 addMenu: function (menu) {
845 var cmds = menu.getCommands();
846 for (var i = 0, len = cmds.length; i < len; i++) {
847 this.cmds.push(cmds[i]);
848 }
849 },
850 getOneCommand: function () {
851 return this.cmds.length ? this.cmds.shift() : null;
852 }
853 };
854
855 var MenuCommand = function () {
856 this.col = [];
857 };
858 MenuCommand.prototype = {
859 addCommand: function (cmd) {
860 this.col.push(cmd);
861 },
862 setCookApi: function (cookApi) {
863 },
864 getTableNum: function () {
865 return 0;
866 },
867 getCommands: function () {
868 return this.col;
869 },
870 execute: function () {
871 CommandQueue.addMenu(this);
872 }
873 };
874
875 var HotCook = function (name) {
876 this.name = name;
877 };
878 HotCook.prototype = {
879 cook: function (tableNum, name) {
880 var cookTime = parseInt(10 * Math.random() + 3);
881 console.log(this.name + '廚師正在為' + tableNum + '號桌做:' + name);
882
883 var me = this;
884 setTimeout(function () {
885 console.log(me.name + '廚師為' + tableNum + '號桌做好了:' + name + ',共計耗時=' + cookTime + '秒');
886 }, cookTime * 1000);
887 },
888 run: function () {
889 var me = this;
890 setTimeout(function () {
891 var cmd;
892
893 while ((cmd = CommandQueue.getOneCommand())) {
894 cmd.setCookApi(me);
895 cmd.execute();
896 }
897 }, 1000);
898 }
899 };
900
901 var Waiter = function () {
902 this.menuCommand = new MenuCommand();
903 };
904 Waiter.prototype = {
905 orderDish: function (cmd) {
906 this.menuCommand.addCommand(cmd);
907 },
908 orderOver: function () {
909 this.menuCommand.execute();
910 }
911 };
912
913 var c1 = new HotCook('張三');
914 c1.run();
915
916 for (var i = 0; i < 5; i++) {
917 var waiter = new Waiter();
918 var chop = new ChopCommand(i);
919 var duck = new DuckCommand(i);
920
921 waiter.orderDish(chop);
922 waiter.orderDish(duck);
923
924 waiter.orderOver();
925 }
926
927 }());
928
929 function test() {
930 // 日志請求
931 // TODO 該示例在寫入文件內(nèi)容的時候并不能把實例的原型對象序列化,
932 // 因此讀取文件內(nèi)容后,反序列化后沒有原型對應(yīng)的方法
933 var fs = require('fs');
934 var Promise = require('d:\\node\\node_modules\\rsvp');
935
936 var FileOpeUtil = {
937 readFile: function (pathName) {
938 var def = Promise.defer();
939
940 fs.open(pathName, 'r', function opened(err, fd) {
941 if (err) {
942 def.reject();
943 fs.close(fd);
944 throw err;
945 }
946
947 var readBuffer = new Buffer(1024);
948 var bufferOffset = 0;
949 var bufferLength = readBuffer.length;
950 var filePosition = null;
951
952 fs.read(
953 fd,
954 readBuffer,
955 bufferOffset,
956 bufferLength,
957 filePosition,
958 function read(err, readBytes) {
959 if (err) {
960 def.reject(err);
961 fs.close(fd);
962 return;
963 }
964
965 if (readBytes >= 0) {
966 try {
967 def.resolve(JSON.parse(readBuffer.slice(0, readBytes).toString('utf8')));
968 } catch (e) {
969 def.reject(e);
970 }
971
972 fs.close(fd);
973 }
974 }
975 );
976 });
977
978 return def.promise;
979 },
980 writeFile: function (pathName, list) {
981 var def = Promise.defer();
982
983 fs.open(pathName, 'w', function opened(err, fd) {
984 if (err) {
985 def.reject();
986 fs.close(fd);
987 throw err;
988 }
989
990 var writeBuffer = new Buffer(JSON.stringify(list));
991 var bufferPosition = 0;
992 var bufferLength = writeBuffer.length;
993 var filePosition = null;
994
995 fs.write(
996 fd,
997 writeBuffer,
998 bufferPosition,
999 bufferLength,
1000 filePosition,
1001 function wrote(err, written) {
1002 if (err) {
1003 def.reject(err);
1004 fs.close(fd);
1005 return;
1006 }
1007
1008 console.log('wrote ' + written + ' bytes');
1009 def.resolve(written);
1010 fs.close(fd);
1011 }
1012 );
1013 });
1014
1015 return def.promise;
1016 }
1017 };
1018
1019 function createCommand(name) {
1020 function Command(tableNum) {
1021 this.cookApi = null;
1022 this.tableNum = tableNum;
1023 }
1024
1025 Command.prototype = {
1026 setCookApi: function (cookApi) {
1027 this.cookApi = cookApi;
1028 },
1029 execute: function () {
1030 this.cookApi.cook(this.tableNum, name);
1031 }
1032 };
1033
1034 return Command;
1035 }
1036
1037 var ChopCommand = createCommand('綠豆排骨煲');
1038 var DuckCommand = createCommand('北京烤鴨');
1039
1040 var MenuCommand = function () {
1041 this.col = [];
1042 };
1043 MenuCommand.prototype = {
1044 addCommand: function (cmd) {
1045 this.col.push(cmd);
1046 },
1047 setCookApi: function (cookApi) {
1048 },
1049 getTableNum: function () {
1050 return 0;
1051 },
1052 getCommands: function () {
1053 return this.col;
1054 },
1055 execute: function () {
1056 CommandQueue.addMenu(this);
1057 }
1058 };
1059
1060 var HotCook = function (name) {
1061 this.name = name;
1062 };
1063 HotCook.prototype = {
1064 cook: function (tableNum, name) {
1065 var cookTime = parseInt(10 * Math.random() + 3);
1066 console.log(this.name + '廚師正在為' + tableNum + '號桌做:' + name);
1067
1068 var me = this;
1069 setTimeout(function () {
1070 console.log(me.name + '廚師為' + tableNum + '號桌做好了:' + name + ',共計耗時=' + cookTime + '秒');
1071 }, cookTime * 1000);
1072 },
1073 run: function () {
1074 var me = this;
1075 setTimeout(function () {
1076 var cmd;
1077
1078 while ((cmd = CommandQueue.getOneCommand())) {
1079 cmd.setCookApi(me);
1080 cmd.execute();
1081 break;
1082 }
1083 }, 1000);
1084 }
1085 };
1086
1087 var Waiter = function () {
1088 this.menuCommand = new MenuCommand();
1089 };
1090 Waiter.prototype = {
1091 orderDish: function (cmd) {
1092 this.menuCommand.addCommand(cmd);
1093 },
1094 orderOver: function () {
1095 this.menuCommand.execute();
1096 }
1097 };
1098
1099
1100 var CommandQueue = {
1101 cmds: [],
1102 addMenu: function (menu) {
1103 var cmds = menu.getCommands();
1104 for (var i = 0, len = cmds.length; i < len; i++) {
1105 this.cmds.push(cmds[i]);
1106 }
1107 FileOpeUtil.writeFile('./test.txt', this.cmds);
1108 },
1109 getOneCommand: function () {
1110 var cmd = null;
1111
1112 if (this.cmds.length) {
1113 cmd = this.cmds.shift();
1114 FileOpeUtil.writeFile('./test.txt', this.cmds);
1115 }
1116
1117 return cmd;
1118 }
1119 };
1120
1121 var FILE_NAME = './test.txt';
1122
1123 FileOpeUtil.readFile(FILE_NAME)
1124 .then(function (data) {
1125 console.log(data);
1126 data.map(function () {
1127
1128 });
1129
1130 CommandQueue.cmds = data;
1131 main();
1132 }, function () {
1133 main();
1134 });
1135
1136 function main() {
1137 var c1 = new HotCook('張三');
1138 c1.run();
1139
1140 for (var i = 0; i < 5; i++) {
1141 var waiter = new Waiter();
1142 var chop = new ChopCommand(i);
1143 var duck = new DuckCommand(i);
1144
1145 waiter.orderDish(chop);
1146 waiter.orderDish(duck);
1147
1148 waiter.orderOver();
1149 }
1150 }
1151 }
1152
1153 /*
1154 用于崩潰恢復(fù)的命令日志
1155
1156 命令日志的一個有趣的用途是在程序崩潰后恢復(fù)其狀態(tài)。在前面這個示例中,可以用XHR把經(jīng)過序列化處理的命令記錄到服務(wù)器上。用戶下次訪問該網(wǎng)頁的時候,系統(tǒng)可以找出這些命令并用其將畫布上的圖案精確恢復(fù)到瀏覽器關(guān)閉時的狀態(tài)。這可以替用戶把應(yīng)用程序狀態(tài)保管下來,以便其撤銷先前的任何一次瀏覽器會話中執(zhí)行的操作。如果應(yīng)用系統(tǒng)比較復(fù)雜,那么這種類型的命令日志會很大的存儲需求。為此你可以提供一個按鈕,用戶可以用它提交到當(dāng)時為止的所有操作,從而清空命令棧。
1157 */
1158
1159 /*
1160 命令模式的適用場合
1161
1162 1.如果需要抽象出需要執(zhí)行的動作,并參數(shù)化這些對象,可以選用命令模式。將這些需要執(zhí)行的動作抽象成為命令,然后實現(xiàn)命令的參數(shù)化配置。
1163 2.如果需要在不同的時刻指定,排列和執(zhí)行請求。將這些請求封裝成為命令對象,然后實現(xiàn)請求隊列化。
1164 3.如果需要支持取消操作,可以選用,通過管理命令對象,能很容易的實現(xiàn)命令的恢復(fù)和重做功能。
1165 4.如果需要支持當(dāng)系統(tǒng)奔潰時,能將系統(tǒng)的操作功能重新執(zhí)行一遍時。將這些操作功能的請求封裝成命令對象,然后實現(xiàn)日志命令,就可以在系統(tǒng)恢復(fù)以后,通過日志獲取命令列表,從而重新執(zhí)行一遍功能。
1166 5.在需要事務(wù)的系統(tǒng)中,命令模式提供了對事務(wù)進行建模的方法。
1167
1168
1169 命令模式之利
1170
1171 1.更松散的耦合
1172 命令模式使得發(fā)起命令的對象--客戶端,和具體實現(xiàn)命令的對象--接收者對象完全解耦,也就是說發(fā)起命令的對象完全不知道具體實現(xiàn)對象是誰,也不知道如何實現(xiàn)。
1173 2.更動態(tài)的控制
1174 命令模式把請求封裝起來,可以動態(tài)地對它進行參數(shù)化,隊列花和日志化等操作,從而使得系統(tǒng)更靈活。
1175 3.很自然的復(fù)合命令
1176 很容易地組合符合命令,也就是宏命令。
1177 4.更好的擴展性
1178
1179
1180
1181 命令模式之弊
1182
1183 如果一個命令對象只包裝了一個方法調(diào)用,而且其唯一目的就是這層對象包裝的話,那么這種做法是一種浪費。如果你不需要命令模式給予的任何額外特性,也不需要具有一致接口的類所帶來的模塊性,那么直接使用方法引用而不是完整的命令對象也許更恰當(dāng)。命令對象也會增加代碼調(diào)試的難度,因為在應(yīng)用了命令模式之后原有的方法之上又多了一層可能出錯的代碼。
1184
1185
1186 相關(guān)模式
1187
1188 命令模式和組合模式
1189 可以組合使用
1190 宏命令的功能就可以使用組合模式。
1191
1192 命令模式和備忘錄模式
1193 可以組合使用
1194 在實現(xiàn)可撤銷功能時,如果采用保存命令執(zhí)行前的狀態(tài),撤銷的時候就把狀態(tài)恢復(fù),就可以考慮使用備忘錄模式。
1195
1196 命令模式和模板方法模式
1197 命令模式可以作為模板方法的一種替代模式,也就是說命令模式可以模仿實現(xiàn)模板方法模式的功能。
1198 */
1199
1200
1201 /* Title: Command
1202 Description: creates objects which encapsulate actions and parameters
1203 */
1204
1205 (function () {
1206
1207 var CarManager = {
1208
1209 /* request information */
1210 requestInfo: function (model, id) {
1211 return 'The purchase info for ' + model + ' with ID ' + id + ' is being processed...';
1212 },
1213
1214 /* purchase the car */
1215 buyVehicle: function (model, id) {
1216 return 'You have successfully purchased Item ' + id + ', a ' + model + '.';
1217 }
1218
1219 };
1220
1221 CarManager.execute = function (commad) {
1222 return CarManager[commad.request](commad.model, commad.carID);
1223 };
1224
1225 var actionA = CarManager.execute({request: 'requestInfo', model: 'Ford Mondeo', carID: '543434'});
1226 console.log(actionA);
1227 var actionB = CarManager.execute({request: 'buyVehicle', model: 'Ford Mondeo', carID: '543434'});
1228 console.log(actionB);
1229
1230 })();
1231
1232
1233 // http://www.joezimjs.com/javascript/javascript-design-patterns-command/
1234 var EnableAlarm = function (alarm) {
1235 this.alarm = alarm;
1236 }
1237 EnableAlarm.prototype.execute = function () {
1238 this.alarm.enable();
1239 }
1240
1241 var DisableAlarm = function (alarm) {
1242 this.alarm = alarm;
1243 }
1244 DisableAlarm.prototype.execute = function () {
1245 this.alarm.disable();
1246 }
1247
1248 var ResetAlarm = function (alarm) {
1249 this.alarm = alarm;
1250 }
1251 ResetAlarm.prototype.execute = function () {
1252 this.alarm.reset();
1253 }
1254
1255 var SetAlarm = function (alarm) {
1256 this.alarm = alarm;
1257 }
1258 SetAlarm.prototype.execute = function () {
1259 this.alarm.set();
1260 }
1261
1262 var alarms = [/* array of alarms */],
1263 i = 0, len = alarms.length;
1264
1265 for (; i < len; i++) {
1266 var enable_alarm = new EnableAlarm(alarms[i]),
1267 disable_alarm = new DisableAlarm(alarms[i]),
1268 reset_alarm = new ResetAlarm(alarms[i]),
1269 set_alarm = new SetAlarm(alarms[i]);
1270
1271 new Button('enable', enable_alarm);
1272 new Button('disable', disable_alarm);
1273 new Button('reset', reset_alarm);
1274 new Button('set', set_alarm);
1275 }
1276
1277
1278 var makeEnableCommand = function (alarm) {
1279 return function () {
1280 alarm.enable();
1281 }
1282 }
1283
1284 var makeDisableCommand = function (alarm) {
1285 return function () {
1286 alarm.disable();
1287 }
1288 }
1289
1290 var makeResetCommand = function (alarm) {
1291 return function () {
1292 alarm.reset();
1293 }
1294 }
1295
1296 var makeSetCommand = function (alarm) {
1297 return function () {
1298 alarm.set();
1299 }
1300 }
1301
1302 </script>
1303 </body>
1304 </html>
?
轉(zhuǎn)載于:https://www.cnblogs.com/webFrontDev/archive/2013/05/13/3075865.html
總結(jié)
以上是生活随笔為你收集整理的javascript设计模式--命令模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Event 事件 - 基础
- 下一篇: gradle idea java ssm