本回内容介绍
上一回,聊了桥接模式,做了一道计算题;介一回,聊组合模式(Composite),官方描述组合模式将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
组合模式特性
这里我理了一下,就组合模式的特性而言:
1,组合模式把对象分为组合对象和叶子对象两种。
2,组合对象和叶子对象实现同一批操作。
3,对组合对象执行的操作可以向下传递到叶子节点进行操作。
这样做带来的好处:
1,解耦,弱化类与类之间的耦合,同样的方法得到抽离处理组合对象和叶子对象;
2,把对象组合成属性结构的对象。
这个也是我在网上看了很多描述后做的总结。这里先看一下,然后看例子,看完例子再来看总结,应该会更有心得,来吧,开始咯。
1.组合模式
这里需要用到之前写过的接口类,不清楚的童鞋看看前面聊过的系列05,这里模拟一个导航菜单,如京东的一级导航,二级导航,三级导航,代码如下:
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>");
}
};
代码量太多,还是把测试部分代码分开,如下:
// 以下是测试的代码
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
作为第一个例子,为了便于大家理解,我基本把注释都写完了,把一下叶子对象的方法省去了,只写了一个方法,更直观方便理解。下一个例子用一个图片库来演示,走你。
2. 组合模式之图片库
图片库可以有选择地隐藏或显示图片库的全部或某一部分(单独的或是部分的)。同上面一个例子一样,一个组合类做库、一个叶子类则是图片本身,如下:
<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;
}
}
测试部分,代码如下:
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运行。
装个逼咯。双12大超市小铺子都在搞活动,又是一阵买买买~~
这一回聊的组合模式,对于刚学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等,这回比较抽象,需要多理解~~
下一回,聊一聊状态模式。
看完点个赞,推荐推荐咯,人气+++,动力才能+++吖,嘿嘿~~
注:此系飞狐原创,转载请注明出处
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。