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