上一回,聊了桥接模式,作了一道计算题;介一回,聊组合模式(Composite),官方描述组合模式将对象组合成树形结构以表示“部分-总体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具备一致性。javascript
这里我理了一下,就组合模式的特性而言:
1,组合模式把对象分为组合对象和叶子对象两种。
2,组合对象和叶子对象实现同一批操做。
3,对组合对象执行的操做能够向下传递到叶子节点进行操做。
这样作带来的好处:
1,解耦,弱化类与类之间的耦合,一样的方法获得抽离处理组合对象和叶子对象;
2,把对象组合成属性结构的对象。
这个也是我在网上看了不少描述后作的总结。这里先看一下,而后看例子,看完例子再来看总结,应该会更有心得,来吧,开始咯。html
这里须要用到以前写过的接口类,不清楚的童鞋看看前面聊过的系列05,这里模拟一个导航菜单,如京东的一级导航,二级导航,三级导航,代码以下:java
var d = document; // 定义组合接口 var CompositeInterface = new Interface('CompositeInterface',['addChild','getChild','href']); // 定义叶子接口 var LeafInterface = new Interface('LeafInterface',['href']); // 定义组合类,并定义名字,类型,子集 var Composite = function(name){ this.name = name; this.type = 'Composite'; this.children = []; } // 组合类的方法实现 Composite.prototype = { // 以前说过不少次的,还原指针 constructor:Composite, // 添加子集 addChild:function(child){ this.children.push(child); return this; }, // 获取子集,这里是组合模式的关键 getChild:function(name){ // 定义一个结果数组 var el = []; // 添加叶子对象的方法 var addLeaf = function(item){ // 判断传入的类型为组合对象的状况 if(item.type==="Composite"){ // 若是为组合对象说明还有下一级,则递归,还记得forEach函数吧,系列01讲过的,不清楚的回过头去看看再回忆一下,这里的arguments.callee是指向函数自己的指针 item.children.forEach(arguments.callee); // 判断若是为叶子对象,则直接添加到结果集 }else if(item.type==="Leaf"){ el.push(item); } }; // 判断传入的导航节点是否存在,而且是否等于当前的节点 if(name&&this.name!==name){ // 遍历没什么好说的 this.children.forEach(function(item){ // 判断传入节点为当前节点而且为组合对象则递归 if(item.name === name&&item.type === 'Composite'){ item.children.forEach(addLeaf); } // 传入的节点非当前节点而且是组合对象则递归 if(item.name !== name&&item.type === 'Composite'){ item.children.forEach(arguments.callee); } // 传入的类型若是是叶子对象,正好是调用的节点,则直接添加到结果集 if(item.name === name&&item.type === 'Leaf'){ el.push(item); } }); // 这里是不传参,或者不等于当前节点的状况 }else{ // 这里的递归同上 this.children.forEach(addLeaf); } return el; }, // 跳转的方法 href:function(name){ // 获取叶子对象 var leaves = this.getChild(name); // 遍历并执行叶子对象的跳转 for(var i=0;i<leaves.length;i++){ leaves[i].href(); } } }; // 定义叶子类,并定义名字,类型,子集 var Leaf = function(name){ this.name = name; this.type = 'Leaf'; } // 定义叶子类的方法 Leaf.prototype = { constructor:Leaf, // 这里就简单写入名字当模拟跳转了 href:function(name){ d.write(this.name+"</br>"); } };
代码量太多,仍是把测试部分代码分开,以下:node
// 如下是测试的代码 var n1 = new Leaf("三级导航1"); var n2 = new Leaf("三级导航2"); var n3 = new Leaf("三级导航3"); var n4 = new Leaf("三级导航4"); var n5 = new Leaf("三级导航5"); var n6 = new Leaf("三级导航6"); // 写一个二级导航1,把前三个放入二级导航1 var nav1 = new Composite("二级导航1"); nav1.addChild(n1); nav1.addChild(n2); nav1.addChild(n3); // 写一个二级导航2,把后三个放入二级导航2 var nav2 = new Composite("二级导航2"); nav2.addChild(n4); nav2.addChild(n5); nav2.addChild(n6); // 写一个一级导航,把两个二级导航放入一级导航 var nav = new Composite(); nav.addChild(nav1); nav.addChild(nav2); // 这里不传则返回所有 nav.href("二级导航1"); // 返回三级导航1,三级导航2,三级导航3
做为第一个例子,为了便于你们理解,我基本把注释都写完了,把一下叶子对象的方法省去了,只写了一个方法,更直观方便理解。下一个例子用一个图片库来演示,走你。编程
图片库能够有选择地隐藏或显示图片库的所有或某一部分(单独的或是部分的)。同上面一个例子同样,一个组合类作库、一个叶子类则是图片自己,以下:数组
<div id="main"></div>
var d = document; // 检查组合对象Composite应该具有的方法 var Composite = new Interface('Composite',['add','remove','getChild']); // 检查组合对象GalleryItem应该具有的方法 var GalleryItem = new Interface('GalleryItem',['hide','show']); // 实现Composite,GalleryItem组合对象类 var DynamicGallery = function(id){ // 定义子集 this.children = []; // 建立dom元素 this.el = d.createElement('div'); // 这个id跟上面个例子的name是同样的,传入名 this.el.id = id; // 这个className跟上面例子的type是同样的,区分层级 this.el.className = 'imageLib'; } // 组合类的方法实现 DynamicGallery.prototype = { constructor:DynamicGallery, // 实现Composite组合对象接口 add : function(child){ // 检测接口 Interface.ensureImplements(child,Composite,GalleryItem); // 添加元素 this.children.push(child); // 添加元素到末尾的方法appendChild,不清楚的童鞋在网上搜搜哈 this.el.appendChild(child.getElement()); }, // 删除节点 remove : function(child){ for(var node, i = 0; node = this.getChild(i); i++){ // 这里判断是否存在,存在则删除 if(node == child){ // 这里用数组的方法splice,不清楚的童鞋网上搜搜,比较有意思的一个方法 this.children.splice(i,1); break; } } // dom元素的删除方法removeChild,不清楚的童鞋网上搜一下吧,嘿嘿~ this.el.removeChild(child.getElement()); }, getChild : function(i){ return this.children[i]; }, // 实现叶子对象 hide : function(){ for(var node, i = 0; node = this.getChild(i); i++){ node.hide(); } this.el.style.display = 'none'; }, // 实现叶子对象 show : function(){ this.el.style.display = 'block'; for(var node, i = 0; node = this.getChild(i); i++){ node.show(); } }, // 获取当前的节点 getElement : function(){ return this.el; } } // 叶子类 var GalleryImage = function(src){ this.el = document.createElement('img'); this.el.className = 'imageLeaf'; this.el.src = src; } // 叶子类的方法实现 GalleryImage.prototype = { constructor:GalleryImage, // 这里的方法都是叶子对象的,已是叶子对象了,处于最底层的,没有下一级了。上一个例子没有写,是由于尽可能少写代码便于理解,这里咱们不定义具体的实现,直接抛出就行了 add : function(){ throw new Error('This is not a instance!'); }, remove : function(){ throw new Error('This is not a instance!'); }, getChild : function(id){ // 判断是不是当前元素,是则返回 if(this.id = id){ return this; } return null; }, // 隐藏 hide : function(){ this.el.style.display = 'none'; }, // 显示 show : function(){ this.el.style.display = 'block'; }, getElement : function(){ return this.el; } }
测试部分,代码以下:app
window.onload = function(){ // 从这开始是测试部分,组合类one,用one来表示层级最高吧 var one = new DynamicGallery('one'); // 这里能够循环多张图片来测试,随便搜点儿图片作测试 var item1 = new GalleryImage('./1.jpg'); var item2 = new GalleryImage('./2.jpg'); var item3 = new GalleryImage('./3.jpg'); // 添加叶子对象到顶级组合类one one.add(item1); one.add(item2); one.add(item3); // 组合类two,层级次于one two = new DynamicGallery('two'); // 一样这里也能够循环多张图片来测试 var item4 = new GalleryImage('./4.jpg'); var item5 = new GalleryImage('./5.jpg'); var item6 = new GalleryImage('./6.jpg'); two.add(item4); two.add(item5); two.add(item6); // 链式操做,后面会聊到 d.getElementById('main').appendChild(one.getElement()); one.add(two); one.show(); // 这里写show,two里的图片则显示 two.hide(); }
这个例子在网上不少,这里我改了下代码,使组合对象和叶子对象更直观,让这两个类来管理图片库,代码能够直接copy运行。dom
装个逼咯。双12大超市小铺子都在搞活动,又是一阵买买买~~ide
这一回聊的组合模式,对于刚学JS面向对象的童鞋,很有难度,不过没关系,困难像弹簧,你懂的呃(的呃要快速连读^_^)~
下面的内容,来聊聊递归,由于这回的组合模式用到了递归,恰好能够学习一下加深印象。函数
官方概述程序调用自身的编程技巧称为递归( recursion)。
// 经典的累加,start简写s,end简写e,开始和结束的数字 function add(s,e){ // 初始化遍历为number类型,默认值0 var num = 0; // 先加第一项 num += s; // 判断首项小于末项则执行 if(s<e){ //这里是关键递归关键啦,argument.callee是指向函数自身的指针 //等同于num += add(s+1,e); num += arguments.callee(s+1,e); } return num; } alert(add(1,100)) // 5050
这里之因此用arguments.callee,好处就在于改变函数名的时候,不用再去该内部的代码,防止出错。
这一回,主要聊了组合模式,递归,其中组合模式还回忆了以前聊过的接口类,数组新特性forEach等,这回比较抽象,须要多理解~~
下一回,聊一聊状态模式。
看完点个赞,推荐推荐咯,人气+++,动力才能+++吖,嘿嘿~~
注:此系飞狐原创,转载请注明出处