1 <!DOCTYPE HTML>
2 <html lang="en-US">
3 <head>
4 <meta charset="utf-8">
5 <title></title>
6 </head>
7 <body>
8 <script>
9 /**
10 * 组合模式
11 *
12 * 定义:
13 * 将对象组合成树型结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
14 *
15 * 本质:
16 * 统一叶对象和组合对象
17 *
18 * 组合模式是一种专为创建web上的动态用户界面而量身定制的模式。使用这种模式,可以用一条命命令在多个对象上激发复杂的或递归行为。这可以简化粘合性代码,使其更容易维护,而那些复杂行为则被委托给各个对象。
19 * 组合模式带来的好处
20 * (1),你可以用同样的方法处理对象的集合与其中的特定子对象。组合对象(composite)与组成它的对象实现了同一批操作。对组合对象执行的这些操作将向下传递到所有的组成对象(constituent object),这样一来所有的组成对象都会执行同样的操作。在存在大批对象的情况下,这是一种非常有效的技术。藉此可以不着痕迹地用一组对象替换一个对象,反之亦然,这有助于弱化各个对象之间的耦合。
21 * (2),它可以用来把一批子对象组织成树形结构,并且使整棵树都可被遍历。所有组合对象都实现了一个用来获取其子对象的方法。借助这个方法,你可以隐藏实现的细节并随心所欲地组织子对象,任何使用这个对象的代码都不会对其内部实现形成依赖。
22 *
23 * 目的:
24 * 让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来操作。
25 *
26 * 对象树:
27 * 组合模式会组合出树型结构,组成这个树型结构所使用的多个组件对象,就自然的形成了对象树。
28 *
29 * 组合模式中的递归
30 * 组合模式中的递归,指的是对象递归组合,不是常说的递归算法。在设计上称作递归关联,是对象关联关系中的一种。
31 *
32 * 透明性的实现
33 * 如果把管理子组件的操作定义在Component中,那么客户端只需要面对Component,而无需关心具体的组件类型,这种实现方式就是透明性的实现。
34 * 但是透明性的实现是以安全性为代价的,因为在Component中定义的一些方法,对于叶子对象来说是没有意义的。
35 * 组合模式的透明性实现,通常的方式是:在Component中声明管理子组件的操作,并在Component中为这些方法提供默认的实现,如果子对象不支持的功能,默认的实现可以是抛出一个例外,来表示不支持这个功能。
36 *
37 * 安全性实现
38 * 如果把管理子组件的操作定义在Composite中,那么客户在使用叶子对象的时候,就不会发生使用添加子组件或是删除子组件的操作了,因为压根就没有这样的功能,这种实现方式是安全的。
39 * 但是这样就必须区分Composite对象还是叶子对象,对客户而言这是不透明的。
40 *
41 * 两种各种方式的选择
42 * 对于组合模式而言,会更看重透明性,毕竟组合模式的功能就是要让用户对叶子对象和组合对象的使用具有一致性。
43 *
44 *
45 *
46 */
47
48 /*
49 组合对象的结构
50 在组合对象的层次体系中有两种类型的对象叶对象和组合对象。这是一个递归定义,但这正是组合模式如此有用的原因所在。一个组合对象由一些别的组合对象和叶对象组成。其中只有叶对象不再包含子对象。叶对象是组合对象中最基本的元素,也是各个操作的落实地点。
51 */
52
53 /*
54 使用组合模式
55 只有同时具备吐下两个条件时才适合使用组合模式:
56 1.存在一批组织成某种层次体系的对象(具体的结构在开发期间可能无法得知)。
57 2.希望对这批对象和其中的一部分对象实施一个操作。
58
59 组合模式擅长于对大批对象进行操作。它专为组织这类对象并把操作从一个层次向下一层次传递而设计。藉此可以弱化对相见的耦合并可互换地使用一些类或示例。按这种模式编写的代码模块化程度更高,也更容易维护。
60 */
61
62 (function () {
63 function Component() {}
64
65 Component.prototype = {
66 someOperation: function () {},
67 addChild: function () {
68 throw new Error('object doesn\'t support this method: addChild');
69 },
70 removeChild: function () {
71 throw new Error('object doesn\'t support this method: removeChild');
72 },
73 getChild: function () {
74 throw new Error('object doesn\'t support this method: getChild');
75 }
76 };
77
78 // 组合对象,通常需要存储子对象,定义有子部件的部件行为
79 function Composite() {
80 this.childComponents = [];
81 }
82
83 Composite.prototype.__proto__ = Component.prototype;
84 Composite.prototype.someOperation = function () {
85 for (var i = 0, len = this.childComponents.length; i < len; i++) {
86 this.childComponents.someOperation();
87 }
88 };
89 Composite.prototype.addChild = function (child) {
90 this.childComponents.push(child);
91 };
92 Composite.prototype.removeChild = function (child) {
93 var childComponent;
94 for (var i = 0, len = this.childComponents.length; i < len; i++) {
95 childComponent = this.childComponents[i];
96
97 if (childComponent == child) return true;
98 }
99
100 return false;
101 };
102 Composite.prototype.getChildren = function (index) {
103 if (index >= 0 && index < this.childComponents.length) {
104 return this.childComponents[index];
105 }
106 return null;
107 };
108
109 // 叶子对象,也子对象不再包含其他子对象
110 function Leaf() {}
111
112 Leaf.prototype.__proto__ = Component.prototype;
113 Leaf.prototype.someOperation = function () {};
114
115 var root = new Composite();
116 var a = new Composite();
117 var b = new Composite();
118
119 var leaf1 = new Leaf();
120 var leaf2 = new Leaf();
121 var leaf3 = new Leaf();
122
123 root.addChild(a);
124 root.addChild(b);
125 root.addChild(leaf1);
126 a.addChild(leaf2);
127 b.addChild(leaf3);
128
129 var o = root.getChildren(1);
130 console.log(o);
131 }());
132
133 (function () {
134 // 父组件引用
135
136 function Component() {
137 this.parent = null;
138 }
139
140 Component.prototype = {
141 getChildren: function () {
142 throw new Error('object doesn\'t support this method');
143 },
144 addChild: function () {
145 throw new Error('object doesn\'t support this method: addChild');
146 },
147 removeChild: function () {
148 throw new Error('object doesn\'t support this method: removeChild');
149 },
150 getChild: function () {
151 throw new Error('object doesn\'t support this method: getChild');
152 },
153 printStruct: function () {
154 throw new Error('object doesn\'t support this method');
155 }
156 };
157
158 function Composite(name) {
159 this.childComponents = [];
160 this.name = name;
161 }
162
163 Composite.prototype.__proto__ = Component.prototype;
164 Composite.prototype.addChild = function (child) {
165 this.childComponents.push(child);
166
167 child.parent = this;
168 };
169 Composite.prototype.removeChild = function (child) {
170 var idx = this.childComponents.indexOf(child);
171
172 if (idx !== -1) {
173 for (var i = 0, len = child.getChildren().length; i < len; i++) {
174 var c = child.getChildren()[i];
175 c.parent = this;
176 this.childComponents.push(c);
177 }
178
179 this.childComponents.splice(idx, 1);
180 }
181 };
182 Composite.prototype.getChildren = function () {
183 return this.childComponents;
184 };
185 Composite.prototype.printStruct = function (preStr) {
186 preStr = preStr || '';
187 console.log(preStr + '+' + this.name);
188 preStr += ' ';
189 for (var i = 0, len = this.childComponents.length; i < len; i++) {
190 var c = this.childComponents[i];
191 c.printStruct(preStr);
192 }
193 };
194
195 function Leaf(name) {
196 this.name = name;
197 }
198
199 Leaf.prototype.__proto__ = Component.prototype;
200 Leaf.prototype.printStruct = function (preStr) {
201 preStr = preStr || '';
202 console.log(preStr + '-' + this.name);
203 };
204
205 var root = new Composite('服装');
206 var c1 = new Composite('男装');
207 var c2 = new Composite('女装');
208
209 var leaf1 = new Leaf('衬衣');
210 var leaf2 = new Leaf('夹克');
211 var leaf3 = new Leaf('裙子');
212 var leaf4 = new Leaf('套装');
213
214 root.addChild(c1);
215 root.addChild(c2);
216 c1.addChild(leaf1);
217 c1.addChild(leaf2);
218 c2.addChild(leaf3);
219 c2.addChild(leaf4);
220
221 root.printStruct();
222 console.log('-----------------------------');
223
224 root.removeChild(c1);
225 root.printStruct();
226 }());
227
228
229 (function () {
230 // 环状引用
231
232 // 应该要检测并避免出现环状引用,否则容易引起死循环,或是同一个功能被操作多次。
233
234 function Component() {
235 this.componentPath = '';
236 }
237
238 Component.prototype = {
239 printStruct: function (preStr) {},
240 getChildren: function () {
241 throw new Error('object doesn\'t support this method');
242 },
243 addChild: function () {
244 throw new Error('object doesn\'t support this method: addChild');
245 },
246 removeChild: function () {
247 throw new Error('object doesn\'t support this method: removeChild');
248 },
249 };
250
251 function Composite(name) {
252 this.name = name;
253 this.childComponents = [];
254 }
255
256 Composite.prototype.__proto__ = Component.prototype;
257 Composite.prototype.addChild = function (child) {
258 this.childComponents.push(child);
259
260 if (!this.componentPath || !this.componentPath.trim().length) {
261 this.componentPath = this.name;
262 }
263
264 if (this.componentPath.startsWith(child.name + '.')) {
265 throw new Error('该组件' + chid.name + ' 已被添加过了');
266 } else {
267 if (this.componentPath.indexOf('.' + child.name) < 0) {
268 child.componentPath = this.componentPath + '.' + child.name;
269 } else {
270 throw new Error('该组件' + child.name + ' 已被添加过了');
271 }
272 }
273 };
274 Composite.prototype.printStruct = function (preStr) {
275 console.log(preStr + '+' + this.name);
276
277 for (var i = 0, len = this.childComponents.length; i < len; i++) {
278 var c = this.childComponents[i];
279 c.printStruct(preStr);
280 }
281 };
282
283 function Leaf(name) {
284 this.name = name;
285 }
286
287 Leaf.prototype.__proto__ = Component.prototype;
288 Leaf.prototype.printStruct = function (preStr) {
289 preStr = preStr || '';
290 console.log(preStr + '-' + this.name);
291 };
292
293 var root = new Composite('服装');
294 var c1 = new Composite('男装');
295 var c2 = new Composite('衬衣');
296
297 root.addChild(c1);
298 c1.addChild(c2);
299 c2.addChild(c1);
300
301 root.printStruct();
302
303 /*
304 当某个组件被删除后,路径发生变化,需要修改所有相关路径记录情况。
305 更好的方式是,使用动态计算路径,每次添加一个组件的时候,动态地递归寻找父组件,然后父组件再找父组件,直到根组件。
306 */
307 }());
308
309
310 // CompositeForm类
311 var CompositeForm = function (id, method, action) {
312 // implements Composite, FormItem
313 this.formComponents = [];
314
315 this.element = document.createElement('form');
316 this.element.id = id;
317 this.element.method = method || 'POST';
318 this.element.action = action || '#';
319 };
320
321 CompositeForm.prototype.add = function (child) {
322 this.formComponents.push(child);
323 this.element.appendChild(child.getElement());
324 };
325 CompositeForm.prototype.remove = function (child) {
326 for (var i = 0, len = this.formComponents.length; i < len; i++) {
327 if (this.formComponents[i] === child) {
328 this.formComponents.splice(i, 1);
329 break;
330 }
331 }
332 };
333 CompositeForm.prototype.getChild = function (i) {
334 return this.formComponents[i];
335 };
336 CompositeForm.prototype.save = function () {
337 for (var i = 0, len = this.formComponents.length; i < len; i++) {
338 this.formComponents[i].save();
339 }
340 };
341 CompositeForm.prototype.getElement = function () {
342 return this.element;
343 };
344 CompositeForm.prototype.restore = function () {
345 for (var i = 0, len = this.formComponents.length; i < len; i++) {
346 this.formComponents[i].restore();
347 }
348 };
349
350
351 // Field叶对象类
352 var Field = function (id) {
353 // implements Composite, FormItem
354 this.id = id;
355 this.element = document.getElementById(id);
356 };
357 Field.prototype.add = function () {
358 };
359 Field.prototype.remove = function () {
360 };
361 Field.prototype.getChild = function () {
362 };
363 Field.prototype.save = function () {
364 setCookie(this.id, this.getValue());
365 };
366 Field.prototype.getElement = function () {
367 return this.element;
368 };
369 Field.prototype.getValue = function () {
370 throw new Error('Unsupported operation on the class Field');
371 };
372 Field.prototype.restore = function () {
373 this.element.value = getCookie(this.id);
374 };
375
376
377 // InputField叶对象类
378 var InputField = function (id, label) {
379 // implements Composite, FormItem
380 Field.call(this, id);
381
382 this.input = document.createElement('input');
383 this.input.id = id;
384 this.input.type = "text";
385 this.label = document.createElement('label');
386 this.label.setAttribute('for', id);
387 var labelTextNode = document.createTextNode(label);
388 this.label.appendChild(labelTextNode);
389
390 this.element = document.createElement('div');
391 this.element.className = 'input-field';
392 this.element.appendChild(this.label);
393 this.element.appendChild(this.input);
394 };
395
396 // Inherit from Field
397 InputField.prototype.__proto__ = Field.prototype;
398
399 InputField.prototype.getValue = function () {
400 return this.input.value;
401 };
402
403
404 var TextareaField = function (id, label) {
405 // implements Composite, FormItem
406 Field.call(this, id);
407
408 this.textarea = document.createElement('textarea');
409 this.textarea.id = id;
410
411 this.label = document.createElement('label');
412 this.label.setAttribute('for', id);
413 var labelTextNode = document.createTextNode(label);
414 this.label.appendChild(labelTextNode);
415
416 this.element = document.createElement('div');
417 this.element.className = 'input-field';
418 this.element.appendChild(this.label);
419 this.element.appendChild(this.textarea);
420 };
421
422 TextareaField.prototype.__proto__ = Field.prototype;
423
424 TextareaField.prototype.getValue = function () {
425 return this.textarea.value;
426 };
427
428
429 var SelectField = function (id, label, options) {
430 Field.call(this, id);
431
432 this.select = document.createElement('select');
433 this.select.id = id;
434 if (typeof options === 'object') {
435 for (var prop in options) {
436 if (!options.hasOwnProperty(prop)) {
437 continue;
438 }
439 var newOption = new Option(prop, options[prop]);
440 this.select.add(newOption, undefined);
441 }
442 }
443
444 this.label = document.createElement('label');
445 this.label.setAttribute('for', id);
446 var labelTextNode = document.createTextNode(label);
447 this.label.appendChild(labelTextNode);
448
449 this.element = document.createElement('div');
450 this.element.className = 'input-field';
451 this.element.appendChild(this.label);
452 this.element.appendChild(this.select);
453 };
454 SelectField.prototype.__proto__ = Field.prototype;
455 SelectField.prototype.getValue = function () {
456 return this.select.options[this.select.selectedIndex].value;
457 };
458
459
460 // 汇合起来
461 var contactForm = new CompositeForm('contact-form', 'POST', 'contact.php');
462 contactForm.add(new InputField('first-name', 'First Name:'));
463 contactForm.add(new InputField('last-name', 'Last Name:'));
464 contactForm.add(new InputField('address', 'Address:'));
465 contactForm.add(new InputField('city', 'City:'));
466 stateArray = {
467 'GD': 'guangdong',
468 'HN': 'hunan',
469 'BJ': 'beijing'
470 };
471 contactForm.add(new SelectField('state', 'State:', stateArray));
472 contactForm.add(new InputField('zip', 'Zip:'));
473 contactForm.add(new TextareaField('comments', 'Comments:'));
474
475 document.body.appendChild(contactForm.getElement());
476 addEvent(window, 'unload', function () {
477 contactForm.save();
478 });
479
480 addEvent(window, 'load', function () {
481 contactForm.restore();
482 });
483
484
485 // 向层次体系中添加类
486 var CompositeFieldset = function (id, legendText) {
487 this.components = {};
488
489 this.element = document.createElement('fieldset');
490 this.element.id = id;
491
492 if (legendText) {
493 this.legend = document.createElement('legend');
494 this.legend.appendChild(document.createTextNode(legendText));
495 this.element.appendChild(this.legend);
496 }
497 };
498
499 CompositeFieldset.prototype.add = function (child) {
500 this.components[child.getElement().id] = child;
501 this.element.appendChild(child.getElement());
502 };
503
504 CompositeFieldset.prototype.remove = function (child) {
505 delete this.components[child.getElement().id];
506 };
507
508 CompositeFieldset.prototype.getChild = function (id) {
509 if (this.components[id] !== undefined) {
510 return this.components[id];
511 } else {
512 return null;
513 }
514 };
515
516 CompositeFieldset.prototype.save = function () {
517 for (var id in this.components) {
518 if (!this.components.hasOwnProperty(id)) {
519 continue;
520 }
521 this.components[id].save();
522 }
523 };
524
525 CompositeFieldset.prototype.restore = function () {
526 for (var id in this.components) {
527 if (!this.components.hasOwnProperty(id)) {
528 continue;
529 }
530 this.components[id].restore();
531 }
532 };
533
534 CompositeFieldset.prototype.getElement = function () {
535 return this.element;
536 };
537
538 var contactForm2 = new CompositeForm('contact-form2', 'POST', '#');
539
540 var nameFieldset = new CompositeFieldset('name-fieldset');
541 nameFieldset.add(new InputField('first-name2', 'First Name:'));
542 nameFieldset.add(new InputField('last-name2', 'Last Name'));
543 contactForm2.add(nameFieldset);
544
545 var addressFieldset = new CompositeFieldset('address-fieldset');
546 addressFieldset.add(new InputField('address2', 'Address:'));
547 addressFieldset.add(new InputField('city2', 'City:'));
548 addressFieldset.add(new SelectField('state2', 'State:', stateArray));
549 addressFieldset.add(new InputField('zip2', 'Zip:'));
550 contactForm2.add(addressFieldset);
551 contactForm2.add(new TextareaField('comments2', 'Comments:'));
552 document.body.appendChild(contactForm2.getElement());
553
554 addEvent(window, 'unload', function () {
555 contactForm2.save();
556 });
557 addEvent(window, 'load', function () {
558 contactForm2.restore();
559 });
560
561
562 /*
563 添加更多操作
564
565 可以为Field的构造函数增加一个参数,用以表明该域是否必须填写,然后基于这个属性实现一个验证方法。可以修改restore方法,以便在没有保存难过数据的情况下将其值设置为默认值。甚至还可以添加一个submit方法,用Ajax请求把所有的值发送到服务器端。由于使用了组合模式,添加这些操作并不需要知道表单具体是什么样子。
566 */
567
568 // 图片库
569
570 // DynamicGallery class.
571 var DynamicGallery = function (id) {
572 // implements Composite, GalleryItem
573 this.children = [];
574
575 this.element = document.createElement('div');
576 this.element.id = id;
577 this.element.className = 'dynamic-gallery';
578 };
579
580 DynamicGallery.prototype = {
581 // implement the Composite interface
582 add: function (child) {
583 this.children.push(child);
584 this.element.appendChild(child.getElement());
585 },
586 remove: function (child) {
587 for (var node, i = 0; node = this.getChild(i); i++) {
588 if (node === child) {
589 this.children.splice(i, 1);
590 break;
591 }
592 }
593 this.element.removeChild(child.getElement());
594 },
595 getChild: function (i) {
596 return this.children[i];
597 },
598 // implement the GalleryItem interface
599 hide: function () {
600 for (var node, i = 0; node = this.getChild(i); i++) {
601 node.hide();
602 }
603 this.element.style.display = 'none';
604 },
605 show: function () {
606 this.element.style.display = 'block';
607 for (var node, i = 0; node = this.getChild(i); i++) {
608 node.show();
609 }
610 },
611 // Helper methods
612 getElement: function () {
613 return this.element;
614 }
615 };
616
617 /*
618 你也许很想用DOM自身作为保存子元素的数据结构。它已经拥有appendChild和removeChild方法,还有childNodes属性面对与存储和获取组合对象的子对象来说这原本非常理想。问题在于这种做法要求每个相关DOM节点都要具有一个反指其包装对象的引用,以便实现所要求的操作。而在某些浏览器中这会导致内存泄漏。一般来说,最好避免让DOM对象反过来引用JS对象。
619 */
620
621 // GalleryImage class.
622 var GalleryImage = function (src) {
623 // implements Composite, GalleryItem
624 this.element = document.createElement('img');
625 this.element.className = 'gallery-image';
626 this.element.src = src;
627 };
628
629 GalleryImage.prototype = {
630 // implements the Composite interface
631 /*
632 this is a leaf node, so we don't
633 implements these methods,we just
634 define them
635 */
636 add: function () {
637 },
638 remove: function () {
639 },
640 getChild: function () {
641 },
642 // implements the GalleryItem interface
643 hide: function () {
644 this.element.style.display = 'none';
645 },
646 show: function () {
647 // restore the display attribute to
648 // its previus setting.
649 this.element.style.display = '';
650 },
651 // Helper methods
652 getElement: function () {
653 return this.element;
654 }
655 };
656
657 var topGallery = new DynamicGallery('top-gallery');
658
659 topGallery.add(new GalleryImage('img/image-1.jpg'));
660 topGallery.add(new GalleryImage('img/image-2.jpg'));
661 topGallery.add(new GalleryImage('img/image-3.jpg'));
662
663 var vacationPhotos = new DynamicGallery('vacation=photos');
664
665 for (var i = 0; i < 30; i++) {
666 vacationPhotos.add(new GalleryImage('img/image-' + i + '.jpg'));
667 }
668
669 topGallery.add(vacationPhotos);
670 topGallery.show();
671 vacationPhotos.hide();
672 document.body.appendChild(topGallery.getElement());
673
674
675 /*
676 组合模式之利
677 1.定义了包含基本对象和组合对象的类层次结构。
678 在组合模式中,基本对象可以被组合成复杂的组合对象,而组合对象又可以组合成更复杂的组合对象,可以不断地递归组合下去,从而构成一个统一的组合对象的类层次结构。
679
680 2.同意了组合对象和叶子对象
681 在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来。
682
683 3.简化了客户端调用
684
685 4.更容易扩展
686 由于客户端是统一地面对Component来操作,因此,新定义的Composite和Leaf子类能够很容易地与已有的结构一起工作,而客户端不需要为增添了新的组件类而改变。
687
688
689 组合模式之弊
690 1.很难限制组合中的组件类型
691 这使得我们必须动态检测组件类型。
692
693
694 何时选用?
695 1.如果你想表示对象的部分--整体层次结构。
696 2.如果你希望统一地使用组合结构中的所有对象。
697
698
699 相关模式
700
701 组合模式与装饰模式
702 可以组合使用。
703 装饰模式在组装多个装饰器对象的时候,是一个装饰器找下一个装饰器,下一个再找下一个,如此递归下去。其实这种结构也可以使用组合模式来帮助构建,这样一来,装饰器对象就相当于组合模式的Composite对象了。
704 要让两个模式能很好地组合使用,通常会让它们有一个公共的父类。因此装饰器必须支持组合模式需要的一些功能,比如,增加,删除子组件。
705
706 组合模式和享元模式
707 可以组合使用。
708 如果组合模式中出现大量相似的组件对象的话,可以考虑使用享元模式来帮助缓存组件对象,这样可以减少内存占用。
709 使用享元模式也是有条件的,如果组件对象的可变化部分的状态能够从组件对象中分离出来,并且组件对象本身不需要向父组件发送请求的话,就可以采用享元模式。
710
711 组合模式和迭代器模式
712 可以组合使用。
713 使用迭代器模式来遍历组合对象的子对象集合,而无需关心具体存放子对象的聚合结构。
714
715 组合模式和访问者模式
716 可以组合使用。
717 访问者模式能够在不修改原有对象结构的情况下,为对象结构中的对象增添新的功能。访问者模式和组合模式合用,可以把原本Composite和Leaf类中的操作和行为都局部化。
718 如果在使用组合模式的时候,预计到以后可能会有增添其他功能的可能,那么可以采用访问者模式,来预留好添加新功能的方式和通道,这样以后再添加新功能的时候,就不需要再修改已有的对象结构和已经实现的功能。
719
720 组合模式和职责链模式
721 可以组合使用。
722 职责链模式要解决的问题是:实现请求的发送者和接收者之间解耦。职责链模式的实现方式是把多个接收者组合起来,构成职责链,然后让请求在这条链上传递,直到有接收者处理这个请求为止。
723 可以应用组合模式来构建这条链,相当于是子组件找父组件,父组件又找父组件,如此递归下去,构成一条处理请求的组件对象链。
724
725 组合模式和命令模式
726 可以组合使用。
727 命令模式中的宏命令就是使用组合模式来组装出来的。
728
729 */
730
731
732 // http://www.dofactory.com/javascript-composite-pattern.aspx
733
734 (function () {
735 var Node = function (name) {
736 this.children = [];
737 this.name = name;
738 }
739
740 Node.prototype = {
741 add: function (child) {
742 this.children.push(child);
743 },
744 remove: function (child) {
745 var length = this.children.length;
746 for (var i = 0; i < length; i++) {
747 if (this.children[i] === child) {
748 this.children.splice(i, 1);
749 return;
750 }
751 }
752 },
753 getChild: function (i) {
754 return this.children[i];
755 },
756 hasChildren: function () {
757 return this.children.length > 0;
758 }
759 }
760
761 // recursively traverse a (sub)tree
762 function traverse(indent, node) {
763
764 log.add(Array(indent++).join("--") + node.name);
765
766 for (var i = 0, len = node.children.length; i < len; i++) {
767 traverse(indent, node.getChild(i));
768 }
769 }
770
771 // logging helper
772 var log = (function () {
773 var log = "";
774 return {
775 add: function (msg) { log += msg + "\n"; },
776 show: function () {
777 alert(log);
778 log = "";
779 }
780 }
781 })();
782
783
784 function run() {
785
786 var tree = new Node("root");
787 var left = new Node("left")
788 var right = new Node("right");
789 var leftleft = new Node("leftleft");
790 var leftright = new Node("leftright");
791 var rightleft = new Node("rightleft");
792 var rightright = new Node("rightright");
793
794 tree.add(left);
795 tree.add(right);
796 tree.remove(right); // note: remove
797 tree.add(right);
798 left.add(leftleft);
799 left.add(leftright);
800 right.add(rightleft);
801 right.add(rightright);
802
803 traverse(1, tree);
804
805 log.show();
806 }
807 }());
808
809
810 </script>
811 </body>
812 </html>