Skip to main content
 首页 » 编程设计

javascript组合模式

2022年07月16日164shihaiming
  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>

本文参考链接:https://www.cnblogs.com/webFrontDev/archive/2013/03/24/2978510.html