资讯专栏INFORMATION COLUMN

听飞狐聊JavaScript设计模式系列12

HitenDev / 1658人阅读

摘要:,对组合对象执行的操作可以向下传递到叶子节点进行操作。组合模式之图片库图片库可以有选择地隐藏或显示图片库的全部或某一部分多带带的或是部分的。

本回内容介绍

上一回,聊了桥接模式,做了一道计算题;介一回,聊组合模式(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");
    }
};

代码量太多,还是把测试部分代码分开,如下:

// 以下是测试的代码
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. 组合模式之图片库

图片库可以有选择地隐藏或显示图片库的全部或某一部分(多带带的或是部分的)。同上面一个例子一样,一个组合类做库、一个叶子类则是图片本身,如下:

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

这里之所以用arguments.callee,好处就在于改变函数名的时候,不用再去该内部的代码,防止出错。

这一回,主要聊了组合模式,递归,其中组合模式还回忆了之前聊过的接口类,数组新特性forEach等,这回比较抽象,需要多理解~~
下一回,聊一聊状态模式。

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78317.html

相关文章

  • 飞狐JavaScript设计模式系列06

    本回内容介绍 上一回聊到JS中模拟接口,装饰者模式,掺元类,分析了backbone的继承源码,感觉还好吧! 介一回,偶们来聊一下在JS单例模式(singleton),单例模式其实运用很广泛,比如:jquery,AngularJS,underscore吖虾米的都是单例模式,来吧,直接开始咯: 1. 单例模式 保证一个类只有一个实例,从全局命名空间里提供一个唯一的访问点来访问该对象。其实之前写过的对象...

    hiYoHoo 评论0 收藏0
  • 飞狐JavaScript设计模式系列02

    摘要:本回内容介绍上一回聊到数据类型,简单的过了一遍,包括个数组新特性等,这一回来聊聊对象,结合数组来实战一些例子,在做题中成长,记忆会更深刻,来吧,开始咯创建实例的方式有两种使用操作符后跟构造函数飞狐使用对象字面量表示法飞狐也可以飞狐这种写法与 本回内容介绍 上一回聊到JS数据类型,简单的过了一遍,包括9个数组新特性等,这一回来聊聊Object对象,结合数组来实战一些例子,在做题中成长,记...

    tangr206 评论0 收藏0
  • 飞狐JavaScript设计模式系列11

    摘要:桥接模式之特权函数特权函数,用一些具有特权的方法作为桥梁以便访问私有空间,可以回忆一下之前的系列。连续自然数分组,计算最多组的个数将至这个连续自然数分成组使每组相加的值相等。个数组中数字最多的一组有个此时的和为。 本回内容介绍 上一回,聊了适配器模式,图片预加载,介一回,聊桥接模式(Bridge),跟之前一样,难度比较小,桥接模式将抽象部分与它的实现部分分离,通过桥接模式联系彼此,同时...

    wanglu1209 评论0 收藏0
  • 飞狐JavaScript设计模式系列10

    摘要:本回内容介绍上一回,聊了代理模式,虚拟代理,图片懒加载,介一回,也比较容易,适配器模式,用一个新的接口对现有类的接口进行包装,处理类与的不匹配。这一回,主要聊了适配器模式,图片预加载,主要还是理解下一回,聊一聊桥接模式,顺便做一做计算题。 本回内容介绍 上一回,聊了代理模式,虚拟代理,图片懒加载,介一回,也比较容易,适配器模式(Adapter),用一个新的接口对现有类的接口进行包装,处...

    yexiaobai 评论0 收藏0
  • 飞狐JavaScript设计模式系列14

    摘要:本回内容介绍上一回,聊了聊状态模式,并介绍了一下介一回,聊链式编程,模拟一下,再模拟一下封装一个库。这一回,主要聊了链式调用,模拟了,尤其是,希望大家能喜欢这次代码分享。下一回,聊一聊的策略模式。 本回内容介绍 上一回,聊了聊状态模式(State),并介绍了一下vue.js;介一回,聊链式编程,模拟一下jQuery,再模拟一下underscore.js,封装一个库。 1. 链式调用 (...

    fox_soyoung 评论0 收藏0

发表评论

0条评论

HitenDev

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<