8

本回内容介绍

上一回,聊了桥接模式,做了一道计算题;介一回,聊组合模式(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等,这回比较抽象,需要多理解~~
下一回,聊一聊状态模式。

看完点个赞,推荐推荐咯,人气+++,动力才能+++吖,嘿嘿~~


注:此系飞狐原创,转载请注明出处


飞狐
2.4k 声望1.4k 粉丝

专注AI量化,技术界最懂金融的CFA金融分析师