组合模式所要解决的问题:
可以使用简单的对象组合成复杂的对象,而这个复杂对象有可以组合成更大的对象。可以把简单这些对象定义成类,然后定义一些容器类来存储这些简单对象。
客户端代码必须区别对象简单对象和容器对象,而实际上大多数情况下用户认为它们是一样的。对这些类区别使用,使得程序更加复杂。递归使用的时候跟麻烦,而我们如何使用递归组合,
使得用户不必对这些类进行区别呢?
概念:
将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
组合模式让你可以优化处理递归或分级数据结构。有许多关于分级数据结构的例子,使得组合模式非常有用武之地。关于分级数据结构的一个普遍性的例子是你每次使用电脑时所遇到的:文件系统。文件系统由目录和文件组成。每个目录都可以装内容。目录的内容可以是文件,也可以是目录。按照这种方式,计算机的文件系统就是以递归结构来组织的。如果你想要描述这样的数据结构,那么你可以使用组合模式Composite。
适用场景:
想表示对象的整体-局部结构,
希望用户忽略组合对象与单个对象的不同,用户可以无差别的使用所有对象。
UML图:
javascript中,对于创建动态用户界面来说,组合模式可以算是为其量身定做的,因为HTML结构正好符合组合模式适用场景的结构。
1. 存在一批组织成某种层次体系的对象。
2. 希望对这批对象或者其中某一部分对象实施一个操作。
组合模式擅长对大批量对象进行操作,专为组织这类对象操作从一个层次向下一个层次传递设计,借此可以弱化对象间的耦合关系并且可以互换使用一些类或者实例。使代码模块化程度更高,维护容易。
下面是JS设计模式中的图片库实例:
首先是图片库的构造函数:
function ImagesStore( id ){ this.children = []; this.element = document.createElement("div"); this.element.id = id; this.element.className = "imgs-store"; } ImagesStore.prototype = { constructor : ImagesStore, 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]; }, show : function(){ this.element.style.display = ''; for( var node, i=0; node = this.getChild(i); i++ ){ node.show(); } }, hide : function(){ for( var node, i=0; node = this.getChild(i); i++ ){ node.hide(); } this.element.style.display = 'none'; }, getElement : function(){ return this.element; } };
从上面的组合对象中我们可以看出,原型上的 hide 和 show 方法不单单只是对于当前的 element 进行处理,还衍生到其包含的每一个叶对象上执行。这边就体现了组合模式的运行机制,一条命令在多个对象上激发复杂的或者递归的行为。在此处我们可以看到每一个对象中的element属性中都指向了该对象的实质DOM对象,一般来说不会有什么大问题,但是需要注意的是千万小心再将DOM对象中放入引用JS对象的属性,这样会导致在一些浏览器中内存泄露,部分内存无法回收。
下面是子节点image的构造函数:
function ImageItem( src ){ this.element = document.createElement("img"); this.element.src = src; this.element.className = "img-item"; } ImageItem.prototype = { constructor : ImagesStore, add : function( child ){ throw new Error("this is image object, no add function"); }, remove : function( child ){ throw new Error("this is image object, no remove function"); }, getChild : function( i ){ throw new Error("this is image object, no getChild function"); }, show : function(){ this.element.style.display = ''; }, hide : function(){ this.element.style.display = 'none'; }, getElement : function(){ return this.element; } };
可以明显看出,对于库节点和IMAGE子节点,除了库节点支持add\remove\getChild等结构上的操作之外,其他方法和IMAGE节点并无差别,而对于上层节点,会递归调用下层节点的相应方法,这就是组合模式要做到的。
调用:
var store = new ImagesStore("first"); store.add( new ImageItem("/img/1.jpg") ); store.add( new ImageItem("/img/2.jpg") );
再在对应的DOM中插入store.element 即可以使用 store.show() 等方法进行控制。并且每个对象之间的耦合非常松散,简单的操作处理复杂的结果。
使用组合模式组织起来的对象具有清晰的层次结构,每当对顶层组合对象执行一个操作的时候,实际上是在对整个结构进行深度优先的节点搜索。但是这些优点都是用操作的代价换取的,比如顶级每执行一次 store.show 实际的操作就是整一颗树形结构的节点均遍历执行一次。
组合模式是将一批子对象组织为树形结构,一条顶层的命令会在操作树中所有的对象。提高了代码的模块化程度,对于动态的HTML界面具有很强的适用性。