-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathComposite.html
812 lines (675 loc) · 27 KB
/
Composite.html
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
<!DOCTYPE HTML>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<script>
/**
* 组合模式
*
* 定义:
* 将对象组合成树型结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
*
* 本质:
* 统一叶对象和组合对象
*
* 组合模式是一种专为创建web上的动态用户界面而量身定制的模式。使用这种模式,可以用一条命命令在多个对象上激发复杂的或递归行为。这可以简化粘合性代码,使其更容易维护,而那些复杂行为则被委托给各个对象。
* 组合模式带来的好处
* (1),你可以用同样的方法处理对象的集合与其中的特定子对象。组合对象(composite)与组成它的对象实现了同一批操作。对组合对象执行的这些操作将向下传递到所有的组成对象(constituent object),这样一来所有的组成对象都会执行同样的操作。在存在大批对象的情况下,这是一种非常有效的技术。藉此可以不着痕迹地用一组对象替换一个对象,反之亦然,这有助于弱化各个对象之间的耦合。
* (2),它可以用来把一批子对象组织成树形结构,并且使整棵树都可被遍历。所有组合对象都实现了一个用来获取其子对象的方法。借助这个方法,你可以隐藏实现的细节并随心所欲地组织子对象,任何使用这个对象的代码都不会对其内部实现形成依赖。
*
* 目的:
* 让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来操作。
*
* 对象树:
* 组合模式会组合出树型结构,组成这个树型结构所使用的多个组件对象,就自然的形成了对象树。
*
* 组合模式中的递归
* 组合模式中的递归,指的是对象递归组合,不是常说的递归算法。在设计上称作递归关联,是对象关联关系中的一种。
*
* 透明性的实现
* 如果把管理子组件的操作定义在Component中,那么客户端只需要面对Component,而无需关心具体的组件类型,这种实现方式就是透明性的实现。
* 但是透明性的实现是以安全性为代价的,因为在Component中定义的一些方法,对于叶子对象来说是没有意义的。
* 组合模式的透明性实现,通常的方式是:在Component中声明管理子组件的操作,并在Component中为这些方法提供默认的实现,如果子对象不支持的功能,默认的实现可以是抛出一个例外,来表示不支持这个功能。
*
* 安全性实现
* 如果把管理子组件的操作定义在Composite中,那么客户在使用叶子对象的时候,就不会发生使用添加子组件或是删除子组件的操作了,因为压根就没有这样的功能,这种实现方式是安全的。
* 但是这样就必须区分Composite对象还是叶子对象,对客户而言这是不透明的。
*
* 两种各种方式的选择
* 对于组合模式而言,会更看重透明性,毕竟组合模式的功能就是要让用户对叶子对象和组合对象的使用具有一致性。
*
*
*
*/
/*
组合对象的结构
在组合对象的层次体系中有两种类型的对象叶对象和组合对象。这是一个递归定义,但这正是组合模式如此有用的原因所在。一个组合对象由一些别的组合对象和叶对象组成。其中只有叶对象不再包含子对象。叶对象是组合对象中最基本的元素,也是各个操作的落实地点。
*/
/*
使用组合模式
只有同时具备吐下两个条件时才适合使用组合模式:
1.存在一批组织成某种层次体系的对象(具体的结构在开发期间可能无法得知)。
2.希望对这批对象和其中的一部分对象实施一个操作。
组合模式擅长于对大批对象进行操作。它专为组织这类对象并把操作从一个层次向下一层次传递而设计。藉此可以弱化对相见的耦合并可互换地使用一些类或示例。按这种模式编写的代码模块化程度更高,也更容易维护。
*/
(function () {
function Component() {}
Component.prototype = {
someOperation: function () {},
addChild: function () {
throw new Error('object doesn\'t support this method: addChild');
},
removeChild: function () {
throw new Error('object doesn\'t support this method: removeChild');
},
getChild: function () {
throw new Error('object doesn\'t support this method: getChild');
}
};
// 组合对象,通常需要存储子对象,定义有子部件的部件行为
function Composite() {
this.childComponents = [];
}
Composite.prototype.__proto__ = Component.prototype;
Composite.prototype.someOperation = function () {
for (var i = 0, len = this.childComponents.length; i < len; i++) {
this.childComponents.someOperation();
}
};
Composite.prototype.addChild = function (child) {
this.childComponents.push(child);
};
Composite.prototype.removeChild = function (child) {
var childComponent;
for (var i = 0, len = this.childComponents.length; i < len; i++) {
childComponent = this.childComponents[i];
if (childComponent == child) return true;
}
return false;
};
Composite.prototype.getChildren = function (index) {
if (index >= 0 && index < this.childComponents.length) {
return this.childComponents[index];
}
return null;
};
// 叶子对象,也子对象不再包含其他子对象
function Leaf() {}
Leaf.prototype.__proto__ = Component.prototype;
Leaf.prototype.someOperation = function () {};
var root = new Composite();
var a = new Composite();
var b = new Composite();
var leaf1 = new Leaf();
var leaf2 = new Leaf();
var leaf3 = new Leaf();
root.addChild(a);
root.addChild(b);
root.addChild(leaf1);
a.addChild(leaf2);
b.addChild(leaf3);
var o = root.getChildren(1);
console.log(o);
}());
(function () {
// 父组件引用
function Component() {
this.parent = null;
}
Component.prototype = {
getChildren: function () {
throw new Error('object doesn\'t support this method');
},
addChild: function () {
throw new Error('object doesn\'t support this method: addChild');
},
removeChild: function () {
throw new Error('object doesn\'t support this method: removeChild');
},
getChild: function () {
throw new Error('object doesn\'t support this method: getChild');
},
printStruct: function () {
throw new Error('object doesn\'t support this method');
}
};
function Composite(name) {
this.childComponents = [];
this.name = name;
}
Composite.prototype.__proto__ = Component.prototype;
Composite.prototype.addChild = function (child) {
this.childComponents.push(child);
child.parent = this;
};
Composite.prototype.removeChild = function (child) {
var idx = this.childComponents.indexOf(child);
if (idx !== -1) {
for (var i = 0, len = child.getChildren().length; i < len; i++) {
var c = child.getChildren()[i];
c.parent = this;
this.childComponents.push(c);
}
this.childComponents.splice(idx, 1);
}
};
Composite.prototype.getChildren = function () {
return this.childComponents;
};
Composite.prototype.printStruct = function (preStr) {
preStr = preStr || '';
console.log(preStr + '+' + this.name);
preStr += ' ';
for (var i = 0, len = this.childComponents.length; i < len; i++) {
var c = this.childComponents[i];
c.printStruct(preStr);
}
};
function Leaf(name) {
this.name = name;
}
Leaf.prototype.__proto__ = Component.prototype;
Leaf.prototype.printStruct = function (preStr) {
preStr = preStr || '';
console.log(preStr + '-' + this.name);
};
var root = new Composite('服装');
var c1 = new Composite('男装');
var c2 = new Composite('女装');
var leaf1 = new Leaf('衬衣');
var leaf2 = new Leaf('夹克');
var leaf3 = new Leaf('裙子');
var leaf4 = new Leaf('套装');
root.addChild(c1);
root.addChild(c2);
c1.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4);
root.printStruct();
console.log('-----------------------------');
root.removeChild(c1);
root.printStruct();
}());
(function () {
// 环状引用
// 应该要检测并避免出现环状引用,否则容易引起死循环,或是同一个功能被操作多次。
function Component() {
this.componentPath = '';
}
Component.prototype = {
printStruct: function (preStr) {},
getChildren: function () {
throw new Error('object doesn\'t support this method');
},
addChild: function () {
throw new Error('object doesn\'t support this method: addChild');
},
removeChild: function () {
throw new Error('object doesn\'t support this method: removeChild');
},
};
function Composite(name) {
this.name = name;
this.childComponents = [];
}
Composite.prototype.__proto__ = Component.prototype;
Composite.prototype.addChild = function (child) {
this.childComponents.push(child);
if (!this.componentPath || !this.componentPath.trim().length) {
this.componentPath = this.name;
}
if (this.componentPath.startsWith(child.name + '.')) {
throw new Error('该组件' + chid.name + ' 已被添加过了');
} else {
if (this.componentPath.indexOf('.' + child.name) < 0) {
child.componentPath = this.componentPath + '.' + child.name;
} else {
throw new Error('该组件' + child.name + ' 已被添加过了');
}
}
};
Composite.prototype.printStruct = function (preStr) {
console.log(preStr + '+' + this.name);
for (var i = 0, len = this.childComponents.length; i < len; i++) {
var c = this.childComponents[i];
c.printStruct(preStr);
}
};
function Leaf(name) {
this.name = name;
}
Leaf.prototype.__proto__ = Component.prototype;
Leaf.prototype.printStruct = function (preStr) {
preStr = preStr || '';
console.log(preStr + '-' + this.name);
};
var root = new Composite('服装');
var c1 = new Composite('男装');
var c2 = new Composite('衬衣');
root.addChild(c1);
c1.addChild(c2);
c2.addChild(c1);
root.printStruct();
/*
当某个组件被删除后,路径发生变化,需要修改所有相关路径记录情况。
更好的方式是,使用动态计算路径,每次添加一个组件的时候,动态地递归寻找父组件,然后父组件再找父组件,直到根组件。
*/
}());
// CompositeForm类
var CompositeForm = function (id, method, action) {
// implements Composite, FormItem
this.formComponents = [];
this.element = document.createElement('form');
this.element.id = id;
this.element.method = method || 'POST';
this.element.action = action || '#';
};
CompositeForm.prototype.add = function (child) {
this.formComponents.push(child);
this.element.appendChild(child.getElement());
};
CompositeForm.prototype.remove = function (child) {
for (var i = 0, len = this.formComponents.length; i < len; i++) {
if (this.formComponents[i] === child) {
this.formComponents.splice(i, 1);
break;
}
}
};
CompositeForm.prototype.getChild = function (i) {
return this.formComponents[i];
};
CompositeForm.prototype.save = function () {
for (var i = 0, len = this.formComponents.length; i < len; i++) {
this.formComponents[i].save();
}
};
CompositeForm.prototype.getElement = function () {
return this.element;
};
CompositeForm.prototype.restore = function () {
for (var i = 0, len = this.formComponents.length; i < len; i++) {
this.formComponents[i].restore();
}
};
// Field叶对象类
var Field = function (id) {
// implements Composite, FormItem
this.id = id;
this.element = document.getElementById(id);
};
Field.prototype.add = function () {
};
Field.prototype.remove = function () {
};
Field.prototype.getChild = function () {
};
Field.prototype.save = function () {
setCookie(this.id, this.getValue());
};
Field.prototype.getElement = function () {
return this.element;
};
Field.prototype.getValue = function () {
throw new Error('Unsupported operation on the class Field');
};
Field.prototype.restore = function () {
this.element.value = getCookie(this.id);
};
// InputField叶对象类
var InputField = function (id, label) {
// implements Composite, FormItem
Field.call(this, id);
this.input = document.createElement('input');
this.input.id = id;
this.input.type = "text";
this.label = document.createElement('label');
this.label.setAttribute('for', id);
var labelTextNode = document.createTextNode(label);
this.label.appendChild(labelTextNode);
this.element = document.createElement('div');
this.element.className = 'input-field';
this.element.appendChild(this.label);
this.element.appendChild(this.input);
};
// Inherit from Field
InputField.prototype.__proto__ = Field.prototype;
InputField.prototype.getValue = function () {
return this.input.value;
};
var TextareaField = function (id, label) {
// implements Composite, FormItem
Field.call(this, id);
this.textarea = document.createElement('textarea');
this.textarea.id = id;
this.label = document.createElement('label');
this.label.setAttribute('for', id);
var labelTextNode = document.createTextNode(label);
this.label.appendChild(labelTextNode);
this.element = document.createElement('div');
this.element.className = 'input-field';
this.element.appendChild(this.label);
this.element.appendChild(this.textarea);
};
TextareaField.prototype.__proto__ = Field.prototype;
TextareaField.prototype.getValue = function () {
return this.textarea.value;
};
var SelectField = function (id, label, options) {
Field.call(this, id);
this.select = document.createElement('select');
this.select.id = id;
if (typeof options === 'object') {
for (var prop in options) {
if (!options.hasOwnProperty(prop)) {
continue;
}
var newOption = new Option(prop, options[prop]);
this.select.add(newOption, undefined);
}
}
this.label = document.createElement('label');
this.label.setAttribute('for', id);
var labelTextNode = document.createTextNode(label);
this.label.appendChild(labelTextNode);
this.element = document.createElement('div');
this.element.className = 'input-field';
this.element.appendChild(this.label);
this.element.appendChild(this.select);
};
SelectField.prototype.__proto__ = Field.prototype;
SelectField.prototype.getValue = function () {
return this.select.options[this.select.selectedIndex].value;
};
// 汇合起来
var contactForm = new CompositeForm('contact-form', 'POST', 'contact.php');
contactForm.add(new InputField('first-name', 'First Name:'));
contactForm.add(new InputField('last-name', 'Last Name:'));
contactForm.add(new InputField('address', 'Address:'));
contactForm.add(new InputField('city', 'City:'));
stateArray = {
'GD': 'guangdong',
'HN': 'hunan',
'BJ': 'beijing'
};
contactForm.add(new SelectField('state', 'State:', stateArray));
contactForm.add(new InputField('zip', 'Zip:'));
contactForm.add(new TextareaField('comments', 'Comments:'));
document.body.appendChild(contactForm.getElement());
addEvent(window, 'unload', function () {
contactForm.save();
});
addEvent(window, 'load', function () {
contactForm.restore();
});
// 向层次体系中添加类
var CompositeFieldset = function (id, legendText) {
this.components = {};
this.element = document.createElement('fieldset');
this.element.id = id;
if (legendText) {
this.legend = document.createElement('legend');
this.legend.appendChild(document.createTextNode(legendText));
this.element.appendChild(this.legend);
}
};
CompositeFieldset.prototype.add = function (child) {
this.components[child.getElement().id] = child;
this.element.appendChild(child.getElement());
};
CompositeFieldset.prototype.remove = function (child) {
delete this.components[child.getElement().id];
};
CompositeFieldset.prototype.getChild = function (id) {
if (this.components[id] !== undefined) {
return this.components[id];
} else {
return null;
}
};
CompositeFieldset.prototype.save = function () {
for (var id in this.components) {
if (!this.components.hasOwnProperty(id)) {
continue;
}
this.components[id].save();
}
};
CompositeFieldset.prototype.restore = function () {
for (var id in this.components) {
if (!this.components.hasOwnProperty(id)) {
continue;
}
this.components[id].restore();
}
};
CompositeFieldset.prototype.getElement = function () {
return this.element;
};
var contactForm2 = new CompositeForm('contact-form2', 'POST', '#');
var nameFieldset = new CompositeFieldset('name-fieldset');
nameFieldset.add(new InputField('first-name2', 'First Name:'));
nameFieldset.add(new InputField('last-name2', 'Last Name'));
contactForm2.add(nameFieldset);
var addressFieldset = new CompositeFieldset('address-fieldset');
addressFieldset.add(new InputField('address2', 'Address:'));
addressFieldset.add(new InputField('city2', 'City:'));
addressFieldset.add(new SelectField('state2', 'State:', stateArray));
addressFieldset.add(new InputField('zip2', 'Zip:'));
contactForm2.add(addressFieldset);
contactForm2.add(new TextareaField('comments2', 'Comments:'));
document.body.appendChild(contactForm2.getElement());
addEvent(window, 'unload', function () {
contactForm2.save();
});
addEvent(window, 'load', function () {
contactForm2.restore();
});
/*
添加更多操作
可以为Field的构造函数增加一个参数,用以表明该域是否必须填写,然后基于这个属性实现一个验证方法。可以修改restore方法,以便在没有保存难过数据的情况下将其值设置为默认值。甚至还可以添加一个submit方法,用Ajax请求把所有的值发送到服务器端。由于使用了组合模式,添加这些操作并不需要知道表单具体是什么样子。
*/
// 图片库
// DynamicGallery class.
var DynamicGallery = function (id) {
// implements Composite, GalleryItem
this.children = [];
this.element = document.createElement('div');
this.element.id = id;
this.element.className = 'dynamic-gallery';
};
DynamicGallery.prototype = {
// implement the Composite interface
add: function (child) {
this.children.push(child);
this.element.appendChild(child.getElement());
},
remove: function (child) {
for (var node, i = 0; node = this.getChild(i); i++) {
if (node === child) {
this.children.splice(i, 1);
break;
}
}
this.element.removeChild(child.getElement());
},
getChild: function (i) {
return this.children[i];
},
// implement the GalleryItem interface
hide: function () {
for (var node, i = 0; node = this.getChild(i); i++) {
node.hide();
}
this.element.style.display = 'none';
},
show: function () {
this.element.style.display = 'block';
for (var node, i = 0; node = this.getChild(i); i++) {
node.show();
}
},
// Helper methods
getElement: function () {
return this.element;
}
};
/*
你也许很想用DOM自身作为保存子元素的数据结构。它已经拥有appendChild和removeChild方法,还有childNodes属性面对与存储和获取组合对象的子对象来说这原本非常理想。问题在于这种做法要求每个相关DOM节点都要具有一个反指其包装对象的引用,以便实现所要求的操作。而在某些浏览器中这会导致内存泄漏。一般来说,最好避免让DOM对象反过来引用JS对象。
*/
// GalleryImage class.
var GalleryImage = function (src) {
// implements Composite, GalleryItem
this.element = document.createElement('img');
this.element.className = 'gallery-image';
this.element.src = src;
};
GalleryImage.prototype = {
// implements the Composite interface
/*
this is a leaf node, so we don't
implements these methods,we just
define them
*/
add: function () {
},
remove: function () {
},
getChild: function () {
},
// implements the GalleryItem interface
hide: function () {
this.element.style.display = 'none';
},
show: function () {
// restore the display attribute to
// its previus setting.
this.element.style.display = '';
},
// Helper methods
getElement: function () {
return this.element;
}
};
var topGallery = new DynamicGallery('top-gallery');
topGallery.add(new GalleryImage('img/image-1.jpg'));
topGallery.add(new GalleryImage('img/image-2.jpg'));
topGallery.add(new GalleryImage('img/image-3.jpg'));
var vacationPhotos = new DynamicGallery('vacation=photos');
for (var i = 0; i < 30; i++) {
vacationPhotos.add(new GalleryImage('img/image-' + i + '.jpg'));
}
topGallery.add(vacationPhotos);
topGallery.show();
vacationPhotos.hide();
document.body.appendChild(topGallery.getElement());
/*
组合模式之利
1.定义了包含基本对象和组合对象的类层次结构。
在组合模式中,基本对象可以被组合成复杂的组合对象,而组合对象又可以组合成更复杂的组合对象,可以不断地递归组合下去,从而构成一个统一的组合对象的类层次结构。
2.同意了组合对象和叶子对象
在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来。
3.简化了客户端调用
4.更容易扩展
由于客户端是统一地面对Component来操作,因此,新定义的Composite和Leaf子类能够很容易地与已有的结构一起工作,而客户端不需要为增添了新的组件类而改变。
组合模式之弊
1.很难限制组合中的组件类型
这使得我们必须动态检测组件类型。
何时选用?
1.如果你想表示对象的部分--整体层次结构。
2.如果你希望统一地使用组合结构中的所有对象。
相关模式
组合模式与装饰模式
可以组合使用。
装饰模式在组装多个装饰器对象的时候,是一个装饰器找下一个装饰器,下一个再找下一个,如此递归下去。其实这种结构也可以使用组合模式来帮助构建,这样一来,装饰器对象就相当于组合模式的Composite对象了。
要让两个模式能很好地组合使用,通常会让它们有一个公共的父类。因此装饰器必须支持组合模式需要的一些功能,比如,增加,删除子组件。
组合模式和享元模式
可以组合使用。
如果组合模式中出现大量相似的组件对象的话,可以考虑使用享元模式来帮助缓存组件对象,这样可以减少内存占用。
使用享元模式也是有条件的,如果组件对象的可变化部分的状态能够从组件对象中分离出来,并且组件对象本身不需要向父组件发送请求的话,就可以采用享元模式。
组合模式和迭代器模式
可以组合使用。
使用迭代器模式来遍历组合对象的子对象集合,而无需关心具体存放子对象的聚合结构。
组合模式和访问者模式
可以组合使用。
访问者模式能够在不修改原有对象结构的情况下,为对象结构中的对象增添新的功能。访问者模式和组合模式合用,可以把原本Composite和Leaf类中的操作和行为都局部化。
如果在使用组合模式的时候,预计到以后可能会有增添其他功能的可能,那么可以采用访问者模式,来预留好添加新功能的方式和通道,这样以后再添加新功能的时候,就不需要再修改已有的对象结构和已经实现的功能。
组合模式和职责链模式
可以组合使用。
职责链模式要解决的问题是:实现请求的发送者和接收者之间解耦。职责链模式的实现方式是把多个接收者组合起来,构成职责链,然后让请求在这条链上传递,直到有接收者处理这个请求为止。
可以应用组合模式来构建这条链,相当于是子组件找父组件,父组件又找父组件,如此递归下去,构成一条处理请求的组件对象链。
组合模式和命令模式
可以组合使用。
命令模式中的宏命令就是使用组合模式来组装出来的。
*/
// http://www.dofactory.com/javascript-composite-pattern.aspx
(function () {
var Node = function (name) {
this.children = [];
this.name = name;
}
Node.prototype = {
add: function (child) {
this.children.push(child);
},
remove: function (child) {
var length = this.children.length;
for (var i = 0; i < length; i++) {
if (this.children[i] === child) {
this.children.splice(i, 1);
return;
}
}
},
getChild: function (i) {
return this.children[i];
},
hasChildren: function () {
return this.children.length > 0;
}
}
// recursively traverse a (sub)tree
function traverse(indent, node) {
log.add(Array(indent++).join("--") + node.name);
for (var i = 0, len = node.children.length; i < len; i++) {
traverse(indent, node.getChild(i));
}
}
// logging helper
var log = (function () {
var log = "";
return {
add: function (msg) { log += msg + "\n"; },
show: function () {
alert(log);
log = "";
}
}
})();
function run() {
var tree = new Node("root");
var left = new Node("left")
var right = new Node("right");
var leftleft = new Node("leftleft");
var leftright = new Node("leftright");
var rightleft = new Node("rightleft");
var rightright = new Node("rightright");
tree.add(left);
tree.add(right);
tree.remove(right); // note: remove
tree.add(right);
left.add(leftleft);
left.add(leftright);
right.add(rightleft);
right.add(rightright);
traverse(1, tree);
log.show();
}
}());
</script>
</body>
</html>