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 * 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
15 *
16 * 本质:
17 * 封装请求
18 *
19 * 命令模式是一种封装方法调用的方式。命令模式与普通函数所有不同。它可以用来对方法调用进行参数化处理和传送,经这样处理过的方法调用可以在任何需要的时候执行。它也可以用来消除调用操作的对象和实现操作对象之间的耦合,这位各种具体的类的更换带来了极大的灵活性。这种模式可以用在许多不同场合,不过它在创建用户界面这一方面非常有用,特别是在需要不受限的(unlimited)取消(undo)操作的时候,它还可以用来替代回调函数,因为它能够提高在对象之间传递的操作的模块化程度。
20 *
21 * 在命令模式中,会定义一个命令的接口,用来约束所有的命令对象,然后提供具体的命令实现,每个命令实现对象是对客户端某个请求的封装,对应于机箱上的按钮,一个机箱上可以有很多按钮,也就相当于会有多个具体的命令实现对象。
22 * 在命令模式中,命令对象并不知道如何处理命令,会有相应的接收者对象来真正执行命令。就像电脑的例子,机箱上的按钮并不知道如何处理功能,而是把这个请求转发给主板,由主办来执行真正的功能,这个主板就相当于命令模式的接收者。
23 * 在命令模式中,命令对象和接收者对象的关系,并不是与生俱来的,需要有一个装配的过程,命令模式中的Client对象可以实现这样的功能。这就相当于在电脑的例子中,有了机箱上的按钮,也有了主板,还需要一个连接线把这个按钮连接到主板上才行。
24 * 命令模式还会提供一个Invoker对象来持有命令对象。就像电脑的例子,机箱上会有多个按钮,这个机箱就相当于命令模式的Invoker对象。这样一来,命令模式的客户端就可以通过Invoker来触发并要求执行相应的命令了,这也相当于真正的客户是按下机箱上的按钮来操作电脑一样。
25 *
26 * 命令模式的关键
27 * 命令模式的关键之处就是把请求封装成对象,也就是命令对象,并定义了统一的执行操作的接口,这个命令对象可以被存储,转发,记录,处理,撤销等,整个命令模式都是围绕这个对象在进行。
28 *
29 * 命令模式的组装和调用
30 * 在命令模式中经常会有一个命令的组装者,用它来维护命令的“虚”实现和真实实现之间的关系。如果是超级智能的命令,也就是说命令对象自己完全实现好了,不需要接收者,那就是命令模式的退化,不需要接受者,自然也不需要组装者了。
31 * 而真正的用户就是具体化请求的内容,然后提交请求进行触发就可以了。真正的用户会通过Invoker来触发命令。
32 * 在实际开发过程中,Client和Invoker可以融合在一起,由客户在使用命令模式的时候,先进行命令对象和接收者的组装,组装完成后,就可以调用命令执行请求。
33 *
34 * 命令模式的接收者
35 * 接收者可以是任意的类,对它没有什么特殊要求,这个对象知道如何真正执行命令的操作,执行时是从Command的实现类里面转调过来。
36 * 一个接收者对象可以处理多个命令,接收者和命令之间没有约定的对应关系。接收者提供的方法个数,名称,功能和命令中的可以不一样,只要能够通过调用接收者的方法来实现命令对应的功能就可以了。
37 *
38 * 命令模式的调用顺序
39 * 使用命令模式的过程分成两个阶段,一个阶段是组装对象和接收者对象的过程,另外一个阶段是触发调用Invoker,来让命令真正的执行。
40 * 组装过程:
41 * 1.创建接收者对象。
42 * 2.创建命令对象,设置命令对象和接收者对象的关系。
43 * 3.创建Invoker对象。
44 * 4.把命令对象设置到Invoker中,让Invoker持有命令对象。
45 * 执行过程:
46 * 1.调用Invoker的方法,触发要求执行命令。
47 * 2.要求持有的命令对象只能执行功能。
48 * 3.要求持有的接收者真正实现功能。
49 *
50 */
51
52 (function () {
53 // 示例代码
54
55 /**
56 * 具体的命令实现对象
57 * @params {Object} receiver 持有相应的接收者对象
58 */
59 function Command(receiver) {
60 this.receiver = receiver;
61 // 命令对象可以有自己的状态
62 this.state = '';
63 }
64
65 Command.prototype.execute = function () {
66 // 通常会转调接收者对象的相应方法,让接收者来真正执行功能
67 this.receiver.action();
68 };
69
70 // 接收者对象
71 function Receiver() {
72 }
73
74 // 真正执行命令相应地操作
75 Receiver.prototype.action = function () {
76 };
77
78 /**
79 * 调用者
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 // 要求命令执行请求
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 命令的结构
107
108 最简形式的命令对象是一个操作和用以调用这个操作的对象的结合体。所有的命令对象都有一个执行操作(execute operation),其用途就是调用命令对象所绑定的操作。在大多数命令对象中,这个操作是一个名为execute或run的方法。使用同样接口的所有命令对象都可以被同等对待,并且可以随意互换,这是命令模式的魅力之一。
109
110 假设你想设计一个网页,客户可以在上面执行一些与自己的账户相关的操作,比如启用和停用某些广告。因为不知道其中的具体广告数量,所以你想设计一个尽可能灵活的用户界面(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 现在有个两个可用在用户界面中的类,它们具有相同的接口。你不知道也不关心adObject的具体实现细节,只要它实现了start和stop方法就行。借助于命令模式,可以实现用户界面对象与广告对象的隔离。
131 */
132
133 /*
134 下面的代码创建的用户界面中,用户名下的每个广告都有两个按钮,分别用于启动和停止广告的轮播:
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类的构造函数有两个参数,一个是按钮上的文字,另一个是命令对象。它会在网页上生成一个按钮,该按钮被点击时会执行那个命令对象的execute方法。这个类也不需要知道所用命令对象的确切实现。因为所有命令对象都实现了execute方法,所以可以把任何一种命令对象提供给UIButton。这有助于创建高度模块化和低耦合的用户界面。
149 */
150
151 /*
152 用闭包创建命令对象
153
154 还有另外一种办法可以用来封装函数。这种办法不需要创建一个具有execute方法的对象,而是把想要执行的方法包装在闭包中。如果想要创建的命令对象像前例中那样只有一个方法。那么这种办法由其方便。现在你不再调用execute方法,因为那个命令可以作为函数直接执行。这样做还可以省却作用域和this关键字的绑定的烦恼。
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 不适用于需要多个命令方法的场合,比如后面要实现取消功能的示例
177 */
178
179 /*
180 客户,调用者和接收者
181
182 这个系统中有三个参与者:客户(client),调用者(invoking object)和接收者(receiving object)。客户负责实例化命令并将其交给调用者。在前面的例子中,for循环中的代码就是客户。它通常被包装为一个对象,但也不是非这样不可。调用者接过命令并将其保存下来。它会在某个时候调用该命令对象的execute方法,或者将其交给另一个潜在的调用者。前例中的调用者就是UIButton类创建的按钮。用户点击它的时候,它就会调用命令对象的execute方法。接收者则是实际执行操作的对象。调用者进行“commandObject.execute()”这种形式的调用时,它所调用的方法将转而以“receiver.action()”这种形式调用恰当的方法。而接收者就是广告对象,它所能执行的操作要么是start方法,要么是stop方法。
183
184 客户创建命令,调用者执行该命令,接收者在命令执行时执行相应操作。
185 所有使用命令模式的系统都有客户和调用者,但不一定有接收者。
186 */
187
188 // 在命令模式中使用接口
189 // If no exception is thrown, youcan safely invoke the
190 // execute operation
191 someCommand.execute();
192
193 // 如果用必报来创建命令函数,只需检查是否为函数即可
194 if (typeof someCommand !== 'function') {
195 throw new Error('Command isn\'t a function');
196 }
197
198
199 // 命令对象的类型
200 /*
201 简单命令对象就是把现有接收者的操作(广告对象的start和stop方法)与调用者(按钮)绑定在一起。这类命令对象最简单,其模块程度也最高。它们与客户,接收者和调用者之间只是松散地偶合在一起:
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 另一种则是那种封装着一套复杂指令的命令对象。这种命令对象实际上没有接受者,因为它自己提供了操作的具体实现。它并不把操作委托给接收者实现,所有用于实现相关操作的代码都包含在其内部:
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方法中也具有一些实现代码。这类命令对象是一个灰色地带:
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 简单命令对象一般用来消除两个对象(接受着和调用者)之间的耦合,而复杂命令对象则一般用来封装不可分的或事务性的指令。
259 */
260
261 // 实例: 菜单项
262 // 菜单组合对象
263 /*
264 接下来要实现的事Menubar,Menu和MenuItem类,作为一个整体,他们要能显示所有可用操作,并且根据要求调用这些操作,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 // 调用者类
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 // 接下来要做的是创建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 // 接收者,负责实现指针移动
476 // Cursor class 实现了命令类所要求的操作
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 // 下面这个装饰者的作用就是在执行一个命令之前先将其压栈
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 // 用户界面类,负责生成必要的HTML元素,并且为其注册click事件监听器,
519 // 这些监听器要么调用execute方法要么调用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类的构造函数也需要把命令栈作为参数传入。这个栈其实就是一个数组。调用经UndoDecorator对象装饰过的命令对象的execute方法时这个命令对象会被压入栈。为了执行取消操作,取消按钮会从命令栈中弹出最近的命令并调用其undo方法。这将逆转刚执行过的操作。
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 // 使用命令日志实现不可逆操作的取消
567 /*
568 在画布上画线很容易,不过要取消这条线的绘制是不可能的。从一个点到另一个点的移动这种操作具有精确的对立操作,执行后者的结果看起来就像前者被逆转了一样。但是对于从A到B画一条线这种操作,从B到A再画一条线是无法逆转前一操作的,这只不过是在第一条线的上方又画一条线而已。
569
570 取消这种操作的唯一办法是清除状态,然后把之前执行过的操作(不含最近那个)一次重做一遍。这很容易办到,为此需要把所有执行过的命令记录在栈中。要想取消一个操作,需要做的就是从栈中弹出最近那个命令并弃之不用,然后清理画布并从头开始重新执行记录下来的所有命令。
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 客户: 只负责发出命令,就是点菜操作。
698 命令对象: 就是点的菜。
699 服务员: 知道真正的接收者是谁,同时持有菜单,当你点菜完毕,服务员就启动命令执行。
700 后厨, 凉菜部: 相当于接收者。
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 + '已经做好,本厨师正在装盘。');
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 // 服务员,负责组合菜单,负责组装每个菜和具体的实现者。
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 该示例在写入文件内容的时候并不能把实例的原型对象序列化,
932 // 因此读取文件内容后,反序列化后没有原型对应的方法
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 用于崩溃恢复的命令日志
1155
1156 命令日志的一个有趣的用途是在程序崩溃后恢复其状态。在前面这个示例中,可以用XHR把经过序列化处理的命令记录到服务器上。用户下次访问该网页的时候,系统可以找出这些命令并用其将画布上的图案精确恢复到浏览器关闭时的状态。这可以替用户把应用程序状态保管下来,以便其撤销先前的任何一次浏览器会话中执行的操作。如果应用系统比较复杂,那么这种类型的命令日志会很大的存储需求。为此你可以提供一个按钮,用户可以用它提交到当时为止的所有操作,从而清空命令栈。
1157 */
1158
1159 /*
1160 命令模式的适用场合
1161
1162 1.如果需要抽象出需要执行的动作,并参数化这些对象,可以选用命令模式。将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置。
1163 2.如果需要在不同的时刻指定,排列和执行请求。将这些请求封装成为命令对象,然后实现请求队列化。
1164 3.如果需要支持取消操作,可以选用,通过管理命令对象,能很容易的实现命令的恢复和重做功能。
1165 4.如果需要支持当系统奔溃时,能将系统的操作功能重新执行一遍时。将这些操作功能的请求封装成命令对象,然后实现日志命令,就可以在系统恢复以后,通过日志获取命令列表,从而重新执行一遍功能。
1166 5.在需要事务的系统中,命令模式提供了对事务进行建模的方法。
1167
1168
1169 命令模式之利
1170
1171 1.更松散的耦合
1172 命令模式使得发起命令的对象--客户端,和具体实现命令的对象--接收者对象完全解耦,也就是说发起命令的对象完全不知道具体实现对象是谁,也不知道如何实现。
1173 2.更动态的控制
1174 命令模式把请求封装起来,可以动态地对它进行参数化,队列花和日志化等操作,从而使得系统更灵活。
1175 3.很自然的复合命令
1176 很容易地组合符合命令,也就是宏命令。
1177 4.更好的扩展性
1178
1179
1180
1181 命令模式之弊
1182
1183 如果一个命令对象只包装了一个方法调用,而且其唯一目的就是这层对象包装的话,那么这种做法是一种浪费。如果你不需要命令模式给予的任何额外特性,也不需要具有一致接口的类所带来的模块性,那么直接使用方法引用而不是完整的命令对象也许更恰当。命令对象也会增加代码调试的难度,因为在应用了命令模式之后原有的方法之上又多了一层可能出错的代码。
1184
1185
1186 相关模式
1187
1188 命令模式和组合模式
1189 可以组合使用
1190 宏命令的功能就可以使用组合模式。
1191
1192 命令模式和备忘录模式
1193 可以组合使用
1194 在实现可撤销功能时,如果采用保存命令执行前的状态,撤销的时候就把状态恢复,就可以考虑使用备忘录模式。
1195
1196 命令模式和模板方法模式
1197 命令模式可以作为模板方法的一种替代模式,也就是说命令模式可以模仿实现模板方法模式的功能。
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>