-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathCommand.html
More file actions
1304 lines (1112 loc) · 42.8 KB
/
Command.html
File metadata and controls
1304 lines (1112 loc) · 42.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<title>命令模式</title>
<meta charset="utf-8">
</head>
<body>
<script>
/**
* 命令模式
*
* 定义:
* 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
*
* 本质:
* 封装请求
*
* 命令模式是一种封装方法调用的方式。命令模式与普通函数所有不同。它可以用来对方法调用进行参数化处理和传送,经这样处理过的方法调用可以在任何需要的时候执行。它也可以用来消除调用操作的对象和实现操作对象之间的耦合,这位各种具体的类的更换带来了极大的灵活性。这种模式可以用在许多不同场合,不过它在创建用户界面这一方面非常有用,特别是在需要不受限的(unlimited)取消(undo)操作的时候,它还可以用来替代回调函数,因为它能够提高在对象之间传递的操作的模块化程度。
*
* 在命令模式中,会定义一个命令的接口,用来约束所有的命令对象,然后提供具体的命令实现,每个命令实现对象是对客户端某个请求的封装,对应于机箱上的按钮,一个机箱上可以有很多按钮,也就相当于会有多个具体的命令实现对象。
* 在命令模式中,命令对象并不知道如何处理命令,会有相应的接收者对象来真正执行命令。就像电脑的例子,机箱上的按钮并不知道如何处理功能,而是把这个请求转发给主板,由主办来执行真正的功能,这个主板就相当于命令模式的接收者。
* 在命令模式中,命令对象和接收者对象的关系,并不是与生俱来的,需要有一个装配的过程,命令模式中的Client对象可以实现这样的功能。这就相当于在电脑的例子中,有了机箱上的按钮,也有了主板,还需要一个连接线把这个按钮连接到主板上才行。
* 命令模式还会提供一个Invoker对象来持有命令对象。就像电脑的例子,机箱上会有多个按钮,这个机箱就相当于命令模式的Invoker对象。这样一来,命令模式的客户端就可以通过Invoker来触发并要求执行相应的命令了,这也相当于真正的客户是按下机箱上的按钮来操作电脑一样。
*
* 命令模式的关键
* 命令模式的关键之处就是把请求封装成对象,也就是命令对象,并定义了统一的执行操作的接口,这个命令对象可以被存储,转发,记录,处理,撤销等,整个命令模式都是围绕这个对象在进行。
*
* 命令模式的组装和调用
* 在命令模式中经常会有一个命令的组装者,用它来维护命令的“虚”实现和真实实现之间的关系。如果是超级智能的命令,也就是说命令对象自己完全实现好了,不需要接收者,那就是命令模式的退化,不需要接受者,自然也不需要组装者了。
* 而真正的用户就是具体化请求的内容,然后提交请求进行触发就可以了。真正的用户会通过Invoker来触发命令。
* 在实际开发过程中,Client和Invoker可以融合在一起,由客户在使用命令模式的时候,先进行命令对象和接收者的组装,组装完成后,就可以调用命令执行请求。
*
* 命令模式的接收者
* 接收者可以是任意的类,对它没有什么特殊要求,这个对象知道如何真正执行命令的操作,执行时是从Command的实现类里面转调过来。
* 一个接收者对象可以处理多个命令,接收者和命令之间没有约定的对应关系。接收者提供的方法个数,名称,功能和命令中的可以不一样,只要能够通过调用接收者的方法来实现命令对应的功能就可以了。
*
* 命令模式的调用顺序
* 使用命令模式的过程分成两个阶段,一个阶段是组装对象和接收者对象的过程,另外一个阶段是触发调用Invoker,来让命令真正的执行。
* 组装过程:
* 1.创建接收者对象。
* 2.创建命令对象,设置命令对象和接收者对象的关系。
* 3.创建Invoker对象。
* 4.把命令对象设置到Invoker中,让Invoker持有命令对象。
* 执行过程:
* 1.调用Invoker的方法,触发要求执行命令。
* 2.要求持有的命令对象只能执行功能。
* 3.要求持有的接收者真正实现功能。
*
*/
(function () {
// 示例代码
/**
* 具体的命令实现对象
* @params {Object} receiver 持有相应的接收者对象
*/
function Command(receiver) {
this.receiver = receiver;
// 命令对象可以有自己的状态
this.state = '';
}
Command.prototype.execute = function () {
// 通常会转调接收者对象的相应方法,让接收者来真正执行功能
this.receiver.action();
};
// 接收者对象
function Receiver() {
}
// 真正执行命令相应地操作
Receiver.prototype.action = function () {
};
/**
* 调用者
*
*/
function Invoker() {
}
/**
* @params {Object} command 持有命令对象
*/
Invoker.prototype.setCommand = function (command) {
this.command = command;
};
// 要求命令执行请求
Invoker.prototype.runCommand = function () {
this.command.execute();
};
new function Client() {
var receiver = new Receiver();
var command = new Command(receiver);
var invoker = new Invoker();
invoker.setCommand(command);
invoker.runCommand();
}();
}());
/*
命令的结构
最简形式的命令对象是一个操作和用以调用这个操作的对象的结合体。所有的命令对象都有一个执行操作(execute operation),其用途就是调用命令对象所绑定的操作。在大多数命令对象中,这个操作是一个名为execute或run的方法。使用同样接口的所有命令对象都可以被同等对待,并且可以随意互换,这是命令模式的魅力之一。
假设你想设计一个网页,客户可以在上面执行一些与自己的账户相关的操作,比如启用和停用某些广告。因为不知道其中的具体广告数量,所以你想设计一个尽可能灵活的用户界面(UI)。为此你打算用命令模式来弱化按钮之类的用户界面元素与其操作之间的耦合。
*/
// 定义两个类,分别用来封装广告的start方法和stop方法
// StopAd command class
var StopAd = function (adObject) {
this.ad = adObject;
};
StopAd.prototype.execute = function () {
this.ad.stop();
};
// StartAd command class
var StartAd = function (adObject) {
this.ad = adObject;
};
StartAd.prototype.execute = function () {
this.ad.start();
};
/*
现在有个两个可用在用户界面中的类,它们具有相同的接口。你不知道也不关心adObject的具体实现细节,只要它实现了start和stop方法就行。借助于命令模式,可以实现用户界面对象与广告对象的隔离。
*/
/*
下面的代码创建的用户界面中,用户名下的每个广告都有两个按钮,分别用于启动和停止广告的轮播:
*/
// implementation code
var ads = getAds();
for (var i = 0, len = ads.length; i < len; i++) {
// Create command objects for starting and stopping the ad
var startCommand = new StartAd(ads[i]);
var stopCommand = new StopAd(ads[i]);
// Create the UI elements that will execute the command on click
new UIButton('Start ' + ads[i].name, startCommand);
new UIButton('stop ' + ads[i].name, stopCommand);
}
/*
UIButton类的构造函数有两个参数,一个是按钮上的文字,另一个是命令对象。它会在网页上生成一个按钮,该按钮被点击时会执行那个命令对象的execute方法。这个类也不需要知道所用命令对象的确切实现。因为所有命令对象都实现了execute方法,所以可以把任何一种命令对象提供给UIButton。这有助于创建高度模块化和低耦合的用户界面。
*/
/*
用闭包创建命令对象
还有另外一种办法可以用来封装函数。这种办法不需要创建一个具有execute方法的对象,而是把想要执行的方法包装在闭包中。如果想要创建的命令对象像前例中那样只有一个方法。那么这种办法由其方便。现在你不再调用execute方法,因为那个命令可以作为函数直接执行。这样做还可以省却作用域和this关键字的绑定的烦恼。
*/
// Command using closures
function makeSart(adObject) {
return function () {
adObject.start();
};
}
function makeStop(adObject) {
return function () {
adObject.stop();
};
}
// Implementation code
var startCommand = makeStart(ads[0]);
var stopCommand = makeStop(ads[0]);
startCommand();
stopCommand();
/*
不适用于需要多个命令方法的场合,比如后面要实现取消功能的示例
*/
/*
客户,调用者和接收者
这个系统中有三个参与者:客户(client),调用者(invoking object)和接收者(receiving object)。客户负责实例化命令并将其交给调用者。在前面的例子中,for循环中的代码就是客户。它通常被包装为一个对象,但也不是非这样不可。调用者接过命令并将其保存下来。它会在某个时候调用该命令对象的execute方法,或者将其交给另一个潜在的调用者。前例中的调用者就是UIButton类创建的按钮。用户点击它的时候,它就会调用命令对象的execute方法。接收者则是实际执行操作的对象。调用者进行“commandObject.execute()”这种形式的调用时,它所调用的方法将转而以“receiver.action()”这种形式调用恰当的方法。而接收者就是广告对象,它所能执行的操作要么是start方法,要么是stop方法。
客户创建命令,调用者执行该命令,接收者在命令执行时执行相应操作。
所有使用命令模式的系统都有客户和调用者,但不一定有接收者。
*/
// 在命令模式中使用接口
// If no exception is thrown, youcan safely invoke the
// execute operation
someCommand.execute();
// 如果用必报来创建命令函数,只需检查是否为函数即可
if (typeof someCommand !== 'function') {
throw new Error('Command isn\'t a function');
}
// 命令对象的类型
/*
简单命令对象就是把现有接收者的操作(广告对象的start和stop方法)与调用者(按钮)绑定在一起。这类命令对象最简单,其模块程度也最高。它们与客户,接收者和调用者之间只是松散地偶合在一起:
*/
// SimpleCommand, a loosely coupled, simple command class.
var SimpleCommand = function (receiver) {
this.receiver = receiver;
};
SimpleCommand.prototype.execute = function () {
this.receiver.action();
};
/*
另一种则是那种封装着一套复杂指令的命令对象。这种命令对象实际上没有接受者,因为它自己提供了操作的具体实现。它并不把操作委托给接收者实现,所有用于实现相关操作的代码都包含在其内部:
*/
// ComplexCommand, a tightly coupled, complex command class.
var ComplexCommand = function () {
this.logger = new Logger();
this.xhrHandler = XhrManager.createXhrHandler();
this.parameters = {};
};
ComplexCommand.prototype = {
setParameter: function (key, value) {
this.parameters[key] = value;
},
execute: function () {
this.logger.log('Executing command');
var postArray = [];
for (var key in this.parameters) {
if (this.parameters.hasOwnProperty(key)) {
postArray.push(key + '=' + this.parameters[key]);
}
}
var postString = postArray.join('&');
this.xhrHandler.request(
'POST',
'script.php',
function () {
},
postString
);
}
};
/*
有些命令对象不但封装了接收者的操作,而且其execute方法中也具有一些实现代码。这类命令对象是一个灰色地带:
*/
// GreyAreaCommand, somewhere between simple and complex
var GreyAreaCommand = function (receiver) {
this.logger = new Logger();
this.receiver = receiver;
};
GreyAreaCommand.prototype.execute = function () {
this.logger.log('Executing command');
this.receiver.prepareAction();
this.receiver.action();
};
/*
简单命令对象一般用来消除两个对象(接受着和调用者)之间的耦合,而复杂命令对象则一般用来封装不可分的或事务性的指令。
*/
// 实例: 菜单项
// 菜单组合对象
/*
接下来要实现的事Menubar,Menu和MenuItem类,作为一个整体,他们要能显示所有可用操作,并且根据要求调用这些操作,Menubar和Menu都是组合对象类,而MenuItem则是叶类。Menubar类保存着所有Menu实例:
*/
// MenuBar class, a composite
var MenuBar = function () {
this.menus = {};
this.element = document.createElement('ul');
this.element.style.display = 'none';
};
MenuBar.prototype = {
add: function (menuObject) {
this.menus[menuObject.name] = menuObject;
this.element.appendChild(this.menus[menuObject.name].getElement());
},
remove: function (name) {
delete this.menus[name];
},
getChild: function (name) {
return this.menus[name];
},
getElement: function () {
return this.element;
},
show: function () {
this.element.style.display = '';
for (var name in this.menus) {
this.menus[name].show();
}
}
};
// Menu class, a composite
var Menu = function (name) {
this.name = name;
this.items = {};
this.element = document.createElement('li');
this.element.style.display = 'none';
this.container = document.createElement('ul');
this.element.appendChild(this.container);
};
Menu.prototype = {
add: function (menuItemObject) {
this.items[menuItemObject.name] = menuItemObject;
this.container.appendChild(this.items[menuItemObject.name].getElement());
},
remove: function () {
delete this.items[name];
},
getChild: function (name) {
return this.items[name];
},
getElement: function () {
return this.element;
},
show: function () {
this.element.style.display = '';
for (var name in this.items) {
this.items[name].show();
}
}
};
// 调用者类
// MenuItem class, a leaf
var MenuItem = function (name, command) {
this.name = name;
this.element = document.createElement('li');
this.element.style.display = 'none';
this.anchor = document.createElement('a');
this.anchor.href = '#';
this.element.appendChild(this.anchor);
this.anchor.innerHTML = this.name;
addEvent(this.anchor, 'click', function (e) {
e = e || window.event;
if (typeof e.preventDefault === 'function') {
e.preventDefault();
} else {
e.returnValue = false;
}
command.execute();
});
};
MenuItem.prototype = {
add: function () {
},
remove: function () {
},
getChild: function () {
},
getElement: function () {
return this.element;
},
show: function () {
this.element.style.display = '';
}
};
// 命令类
// MenuCommand class, a command object
var MenuCommand = function (action) {
this.action = action;
};
MenuCommand.prototype.execute = function () {
this.action.action();
};
// Receiver objects, instantiated from existing classes
var Test1 = function () {
console.log('test1');
};
Test1.prototype = {
action: function () {
console.log('this is test1 fn1');
}
};
var Test2 = function () {
console.log('test2');
};
Test2.prototype = {
action: function () {
console.log('this is test2 fn1');
}
};
var Test3 = function () {
console.log('test3');
};
var test1 = new Test1();
var test2 = new Test2();
var test3 = new Test3();
// Create the menu bar
var appMenuBar = new MenuBar();
// The File menu
var fileMenu = new Menu('File');
var test1Command1 = new MenuCommand(test1);
fileMenu.add(new MenuItem('test1-1', test1Command1));
appMenuBar.add(fileMenu);
var insertMenu = new Menu('Insert');
var test2Command2 = new MenuCommand(test2);
insertMenu.add(new MenuItem('test2-1', test2Command2));
appMenuBar.add(insertMenu);
document.body.appendChild(appMenuBar.getElement());
appMenuBar.show();
(function () {
// 补偿式或者反操作式
// 取消操作和命令日志
// ReversibleCommand interface
var ReversibleCommand = new Interface('ReversibleCommand', ['execute', 'undo']);
// 接下来要做的是创建4个命令类,
// 它们分别用来向上下左右四个方向移动指针:
var MoveUp = function (cursor) {
this.cursor = cursor;
};
MoveUp.prototype = {
execute: function () {
this.cursor.move(0, -10);
},
undo: function () {
this.cursor.move(0, 10);
}
};
var MoveDown = function (cursor) {
this.cursor = cursor;
};
MoveDown.prototype = {
execute: function () {
this.cursor.move(0, 10);
},
undo: function () {
this.cursor.move(0, -10);
}
};
var MoveLeft = function (cursor) {
this.cursor = cursor;
};
MoveLeft.prototype = {
execute: function () {
this.cursor.move(-10, 0);
},
undo: function () {
this.cursor.move(10, 0);
}
};
var MoveRight = function (cursor) {
this.cursor = cursor;
};
MoveRight.prototype = {
execute: function () {
this.cursor.move(10, 0);
},
undo: function () {
this.cursor.move(-10, 0);
}
};
// 接收者,负责实现指针移动
// Cursor class 实现了命令类所要求的操作
var Cursor = function (width, height, parent) {
this.width = width;
this.height = height;
this.position = {
x: width / 2,
y: height / 2
};
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
parent.appendChild(this.canvas);
this.ctx = this.canvas.getContext('2d');
this.ctx.fillStyle = '#cc0000';
this.move(0, 0);
};
Cursor.prototype.move = function (x, y) {
this.position.x += x;
this.position.y += y;
this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.fillRect(this.position.x, this.position.y, 3, 3);
};
// 下面这个装饰者的作用就是在执行一个命令之前先将其压栈
// UndoDecorator class
var UndoDecorator = function (command, undoStack) {
this.command = command;
this.undoStack = undoStack;
};
UndoDecorator.prototype = {
execute: function () {
this.undoStack.push(this.command);
this.command.execute();
},
undo: function () {
this.command.undo();
}
};
// 用户界面类,负责生成必要的HTML元素,并且为其注册click事件监听器,
// 这些监听器要么调用execute方法要么调用undo方法:
// CommandButton class
var CommandButton = function (label, command, parent) {
this.element = document.createElement('button');
this.element.innerHTML = label;
parent.appendChild(this.element);
addEvent(this.element, 'click', function () {
command.execute();
});
};
// UndoButton class
var UndoButton = function (label, parent, undoStack) {
this.element = document.createElement('button');
this.element.innerHTML = label;
parent.appendChild(this.element);
addEvent(this.element, 'click', function () {
if (undoStack.length === 0) return;
var lastCommand = undoStack.pop();
lastCommand.undo();
});
};
/*
像UndoDecorator类一样,UndoButton类的构造函数也需要把命令栈作为参数传入。这个栈其实就是一个数组。调用经UndoDecorator对象装饰过的命令对象的execute方法时这个命令对象会被压入栈。为了执行取消操作,取消按钮会从命令栈中弹出最近的命令并调用其undo方法。这将逆转刚执行过的操作。
*/
// Implementation code
var body = document.body;
var cursor = new Cursor(400, 400, body);
var undoStack = [];
var upCommand = new UndoDecorator(new MoveUp(cursor), undoStack);
var downCommand = new UndoDecorator(new MoveDown(cursor), undoStack);
var leftCommand = new UndoDecorator(new MoveLeft(cursor), undoStack);
var rightCommand = new UndoDecorator(new MoveRight(cursor), undoStack);
var upButton = new CommandButton('Up', upCommand, body);
var downButton = new CommandButton('Down', downCommand, body);
var leftButton = new CommandButton('Left', leftCommand, body);
var rightButton = new CommandButton('Right', rightCommand, body);
var undoButton = new UndoButton('Undo', body, undoStack);
}());
(function () {
// 使用命令日志实现不可逆操作的取消
/*
在画布上画线很容易,不过要取消这条线的绘制是不可能的。从一个点到另一个点的移动这种操作具有精确的对立操作,执行后者的结果看起来就像前者被逆转了一样。但是对于从A到B画一条线这种操作,从B到A再画一条线是无法逆转前一操作的,这只不过是在第一条线的上方又画一条线而已。
取消这种操作的唯一办法是清除状态,然后把之前执行过的操作(不含最近那个)一次重做一遍。这很容易办到,为此需要把所有执行过的命令记录在栈中。要想取消一个操作,需要做的就是从栈中弹出最近那个命令并弃之不用,然后清理画布并从头开始重新执行记录下来的所有命令。
*/
// Movement commands
var MoveUp = function (cursor) {
this.cursor = cursor;
};
MoveUp.prototype = {
execute: function () {
this.cursor.move(0, -10);
}
};
var MoveDown = function (cursor) {
this.cursor = cursor;
};
MoveDown.prototype = {
execute: function () {
this.cursor.move(0, 10);
}
};
var MoveLeft = function (cursor) {
this.cursor = cursor;
};
MoveLeft.prototype = {
execute: function () {
this.cursor.move(-10, 0);
}
};
var MoveRight = function (cursor) {
this.cursor = cursor;
};
MoveRight.prototype = {
execute: function () {
this.cursor.move(10, 0);
}
};
// Cursor class, with an internal command stack
var Cursor = function (width, height, parent) {
this.width = width;
this.height = height;
this.commandStack = [];
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
parent.appendChild(this.canvas);
this.ctx = this.canvas.getContext('2d');
this.ctx.strokeStyle = '#cc0000';
this.move(0, 0);
};
Cursor.prototype = {
move: function (x, y) {
var that = this;
this.commandStack.push(function () {
that.lineTo(x, y);
});
this.executeCommands();
},
lineTo: function (x, y) {
this.position.x += x;
this.position.y += y;
this.ctx.lineTo(this.position.x, this.position.y);
},
executeCommands: function () {
this.position = {
x: this.width / 2,
y: this.height / 2
};
this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.beginPath();
this.ctx.moveTo(this.position.x, this.position.y);
for (var i = 0, len = this.commandStack.length; i < len; i++) {
this.commandStack[i]();
}
this.ctx.stroke();
},
undo: function () {
this.commandStack.pop();
this.executeCommands();
}
};
// UndoButton class
var UndoButton = function (label, parent, cursor) {
this.element = document.createElement('button');
this.element.innerHTML = label;
parent.appendChild(this.element);
addEvent(this.element, 'click', function () {
cursor.undo();
});
};
// CommandButton class
var CommandButton = function (label, command, parent) {
this.element = document.createElement('button');
this.element.innerHTML = label;
parent.appendChild(this.element);
addEvent(this.element, 'click', function () {
command.execute();
});
};
var body = document.body;
var cursor = new Cursor(400, 400, body);
var upCommand = new MoveUp(cursor);
var downCommand = new MoveDown(cursor);
var leftCommand = new MoveLeft(cursor);
var rightCommand = new MoveRight(cursor);
var upButton = new CommandButton('Up', upCommand, body);
var downButton = new CommandButton('Down', downCommand, body);
var leftButton = new CommandButton('Left', leftCommand, body);
var rightButton = new CommandButton('Right', rightCommand, body);
var undoButton = new UndoButton('Undo', body, cursor);
}());
(function () {
// 宏命令
/*
去饭店吃饭过程。
客户: 只负责发出命令,就是点菜操作。
命令对象: 就是点的菜。
服务员: 知道真正的接收者是谁,同时持有菜单,当你点菜完毕,服务员就启动命令执行。
后厨, 凉菜部: 相当于接收者。
菜单命令包含多个命令对象
*/
// 坐热菜的厨师
var HotCook = function () {
};
HotCook.prototype = {
cook: function (name) {
console.log('本厨师正在做:' + name);
}
};
// 做凉菜的厨师
var CoolCook = function () {
};
CoolCook.prototype = {
cook: function (name) {
console.log('凉菜' + name + '已经做好,本厨师正在装盘。');
}
}
// 定义了三道菜,每道菜是一个命令对象
var DuckCommand = function () {
this.cookApi = null;
};
DuckCommand.prototype = {
constructor: DuckCommand,
setCookApi: function (cookApi) {
this.cookApi = cookApi;
},
execute: function () {
this.cookApi.cook('北京烤鸭');
}
};
var ChopCommand = function () {
this.cookApi = null;
};
ChopCommand.prototype = {
constructor: ChopCommand,
setCookApi: function (cookApi) {
this.cookApi = cookApi;
},
execute: function () {
this.cookApi.cook('绿豆排骨煲');
}
};
var PorkCommand = function () {
this.cookApi = null;
};
PorkCommand.prototype = {
constructor: PorkCommand,
setCookApi: function (cookApi) {
this.cookApi = cookApi;
},
execute: function () {
this.cookApi.cook('蒜泥白肉');
}
};
// 菜单对象,宏命令对象
var MenuCommand = function () {
var col = [];
this.addCommand = function (cmd) {
col.push(cmd);
};
this.execute = function () {
for (var i = 0 , len = col.length; i < len; i++) {
col[i].execute();
}
};
};
// 服务员,负责组合菜单,负责组装每个菜和具体的实现者。
var Waiter = function () {
var menuCommand = new MenuCommand();
// 客户点菜
this.orderDish = function (cmd) {
var hotCook = new HotCook();
var coolCook = new CoolCook();
if (cmd instanceof DuckCommand) {
cmd.setCookApi(hotCook);
} else if (cmd instanceof ChopCommand) {
cmd.setCookApi(hotCook);
} else if (cmd instanceof PorkCommand) {
cmd.setCookApi(coolCook);
}
menuCommand.addCommand(cmd);
};
// 点菜完毕
this.orderOver = function () {
menuCommand.execute();
};
};
var waiter = new Waiter();
var chop = new ChopCommand();
var duck = new DuckCommand();
var pork = new PorkCommand();
waiter.orderDish(chop);
waiter.orderDish(duck);
waiter.orderDish(pork);
waiter.orderOver();
}());
(function () {
// 队列请求
function createCommand(name) {
function Command(tableNum) {
this.cookApi = null;
this.tableNum = tableNum;
}
Command.prototype = {
setCookApi: function (cookApi) {
this.cookApi = cookApi;
},
execute: function () {
this.cookApi.cook(this.tableNum, name);
}
};
return Command;
}
var ChopCommand = createCommand('绿豆排骨煲');
var DuckCommand = createCommand('北京烤鸭');
var CommandQueue = {
cmds: [],
addMenu: function (menu) {
var cmds = menu.getCommands();
for (var i = 0, len = cmds.length; i < len; i++) {
this.cmds.push(cmds[i]);
}
},
getOneCommand: function () {
return this.cmds.length ? this.cmds.shift() : null;
}
};
var MenuCommand = function () {
this.col = [];
};
MenuCommand.prototype = {
addCommand: function (cmd) {
this.col.push(cmd);
},
setCookApi: function (cookApi) {
},
getTableNum: function () {
return 0;
},
getCommands: function () {
return this.col;
},
execute: function () {
CommandQueue.addMenu(this);
}
};
var HotCook = function (name) {
this.name = name;
};
HotCook.prototype = {
cook: function (tableNum, name) {
var cookTime = parseInt(10 * Math.random() + 3);
console.log(this.name + '厨师正在为' + tableNum + '号桌做:' + name);
var me = this;
setTimeout(function () {
console.log(me.name + '厨师为' + tableNum + '号桌做好了:' + name + ',共计耗时=' + cookTime + '秒');
}, cookTime * 1000);
},
run: function () {
var me = this;
setTimeout(function () {
var cmd;
while ((cmd = CommandQueue.getOneCommand())) {
cmd.setCookApi(me);
cmd.execute();
}
}, 1000);
}
};
var Waiter = function () {
this.menuCommand = new MenuCommand();
};
Waiter.prototype = {
orderDish: function (cmd) {
this.menuCommand.addCommand(cmd);
},
orderOver: function () {
this.menuCommand.execute();
}
};
var c1 = new HotCook('张三');
c1.run();
for (var i = 0; i < 5; i++) {
var waiter = new Waiter();
var chop = new ChopCommand(i);
var duck = new DuckCommand(i);
waiter.orderDish(chop);
waiter.orderDish(duck);
waiter.orderOver();
}
}());
function test() {
// 日志请求
// TODO 该示例在写入文件内容的时候并不能把实例的原型对象序列化,
// 因此读取文件内容后,反序列化后没有原型对应的方法
var fs = require('fs');
var Promise = require('d:\\node\\node_modules\\rsvp');
var FileOpeUtil = {
readFile: function (pathName) {
var def = Promise.defer();
fs.open(pathName, 'r', function opened(err, fd) {
if (err) {
def.reject();
fs.close(fd);
throw err;
}
var readBuffer = new Buffer(1024);
var bufferOffset = 0;
var bufferLength = readBuffer.length;
var filePosition = null;
fs.read(
fd,
readBuffer,
bufferOffset,
bufferLength,
filePosition,
function read(err, readBytes) {
if (err) {
def.reject(err);
fs.close(fd);
return;
}
if (readBytes >= 0) {
try {
def.resolve(JSON.parse(readBuffer.slice(0, readBytes).toString('utf8')));
} catch (e) {
def.reject(e);
}
fs.close(fd);
}
}
);
});
return def.promise;
},
writeFile: function (pathName, list) {
var def = Promise.defer();
fs.open(pathName, 'w', function opened(err, fd) {
if (err) {
def.reject();
fs.close(fd);
throw err;
}
var writeBuffer = new Buffer(JSON.stringify(list));
var bufferPosition = 0;
var bufferLength = writeBuffer.length;
var filePosition = null;
fs.write(
fd,
writeBuffer,
bufferPosition,
bufferLength,
filePosition,