javascript的设计模式之五[组合模式(Composite)]

概述

它又是部分-整体的模式,元素有两种形式,一种是简单元素,一种是复杂元素,其中复杂元素是简单元素的组成,所以客户程序要操作复杂元素时,有时候它并不想了解内部包括哪些简单元素,但是又想能够保持简单元素的简易操作性,于是,就有了组合模式,它可以抽象成一个树状结构,其中简单元素为叶子结点,而复杂元素为非叶子结点。

定义

组合模式允许你将对象组合成树形结构以表示“整体/部分”的层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

类图




实例分析

这里我给出一个利用组合模式设计的菜单导航:

先看下效果图:


现在开始分析通过组合模式如何实现它:

1. 引用InterfaceAndClass.js文件,作为接口的构造和类的继承,这个不多说了,详细请看前面的文章;

2. 添加MenuComponent.js文件,作为Component接口或者抽象类,这里我用了接口的形式:
程序代码 程序代码
var MenuComponent = new Interface("MenuComponent", [["getValue"]]);


3. 添加MenuItem.js文件,作为Leaf简单元素,这里指菜单的叶子结点:
程序代码 程序代码

function MenuItem(text, title, href) {
    this.text = text;
    this.title = title;
    this.href = href;
    Interface.registerImplements(this, MenuComponent);
}

MenuItem.prototype = {
    getValue : function() {
        //
    }
}

其中text为菜单上显示的文本,title为提示文本符,href为链接导向;并且让它继承MenuComponent接口;

4. 添加Menu.js文件,作为Composite复合元素,它是由一系列的Leaf简单元素组成的:
程序代码 程序代码

function Menu(text, title, href) {
    this.menuComponents = new Array();
    this.text = text;
    this.title = title;
    this.href = href;
    Interface.registerImplements(this, MenuComponent);
}

Menu.prototype = {
    getValue : function() {
        //
    },
    add : function(component) {
        this.menuComponents.push(component);
    },
    remove : function(component) {
        for(var i = 0, len = this.menuComponents.length; i < len; i++)
        {
            if(this.menuComponents[i] == component)
            {
                this.menuComponents.splice(i,1);
                break;
            }
        }
    },
    removeAt : function(index) {
        if(this.menuComponents.length <= index)
        {
            this.menuComponents.splice(index, 1);
        }
        else
        {
            throw new Error("索引操作数组超过上限");
        }
    }
}

它继承MenuAComponent接口,因此它和MenuItem使用同一个接口,这样可以统一简单元素和复杂元素的方法调用,add方法用来添加component类,这里的component可以是Menu,也可以是MenuItem;remove方法用来删除指定的component 类;removeAt方法用过索引删除相应的component类;getValue方法取得菜单数据;

让我们先从实例上得到一个树状图:

从图上可以看出,实际上菜单1,菜单2-1,菜单2-2-1,菜单2-2-2,菜单2-3,菜单3-1,菜单4都是属于Leaf,即MenuItem类;

菜单2,菜单2-2,菜单3都属于Composite,即Menu类;

这样Menu类的GetValue方法可以通过遍历它的子结点数组,得到所有子结点菜单数据;
程序代码 程序代码

for(var i = 0, len = this.menuComponents.length; i < len; i++)
{
    str += this.menuComponents[i].getValue();
}

其中this.menuComponents[i].getValue()可以是Menu类的getValue方法,也可以是MenuItem类的getValue方法;

这样就可以完成了所有结点的遍历。

然后给出MenuItem.js和Menu.js的完整代码如下:
MenuItem.js
程序代码 程序代码

function MenuItem(text, title, href) {
    this.text = text;
    this.title = title;
    this.href = href;
    Interface.registerImplements(this, MenuComponent);
}

MenuItem.prototype = {
    getValue : function() {
        var str = "<li class=\"Menu-Leaf\" title=\"" + this.title + "\"><a href=\"" + this.href + "\">" + this.text + "</a></li>";
        return str;
    }
}

Menu.js
程序代码 程序代码

function Menu(text, title, href) {
    this.menuComponents = new Array();
    this.text = text;
    this.title = title;
    this.href = href;
    Interface.registerImplements(this, MenuComponent);
}

Menu.prototype = {
    getValue : function() {
        if(this.menuComponents.length == 0)
        {
            throw new Error(this.text + "菜单下没有子菜单");
        }
        var str = "<li class=\"Menu-WithChildren\" title=\"" + this.title + "\"><a class=\"Menu-Link\" href=\"" + this.href + "\">" + this.text + "</a>";
        str += "<ul>";
        for(var i = 0, len = this.menuComponents.length; i < len; i++)
        {
            str += this.menuComponents[i].getValue();
        }
        str += "</ul>";
        return str;
    },
    add : function(component) {
        this.menuComponents.push(component);
    },
    remove : function(component) {
        for(var i = 0, len = this.menuComponents.length; i < len; i++)
        {
            if(this.menuComponents[i] == component)
            {
                this.menuComponents.splice(i,1);
                break;
            }
        }
    },
    removeAt : function(index) {
        if(this.menuComponents.length <= index)
        {
            this.menuComponents.splice(index, 1);
        }
        else
        {
            throw new Error("索引操作数组超过上限");
        }
    }
}

5. 接着添加一个Menu操作类MenuOpr:
程序代码 程序代码

var MenuOpr = {
    list : new Array(),
    add : function(component) {
        this.list.push(component);
    },
    print : function(container) {
        var str = "<ul class=\"Menu\">";
        for(var i = 0, len = this.list.length; i < len; i++) {
            str += this.list[i].getValue();
        }
        document.getElementById(container).innerHTML = str + "</ul>";
    }
}

6. 最后利用组合模式编写调用代码:
程序代码 程序代码

var menu1 = new MenuItem("菜单1","菜单1","#");

var menu2 = new Menu("菜单2","菜单2","#");
var menu2_1 = new MenuItem("菜单2-1", "菜单2-1", "#");
var menu2_2 = new Menu("菜单2-2", "菜单2-2", "#");
var menu2_2_1 = new MenuItem("菜单2-2-1", "菜单2-2-1", "#");
var menu2_2_2 = new MenuItem("菜单2-2-2", "菜单2-2-2", "#");
var menu2_3 = new MenuItem("菜单2-3","菜单2-3","#");
menu2.add(menu2_1);
menu2.add(menu2_2);
menu2_2.add(menu2_2_1);
menu2_2.add(menu2_2_2);
menu2.add(menu2_3);

var menu3 = new Menu("菜单3","菜单3","#");
var menu3_1 = new MenuItem("菜单3-1","菜单3-1","#");
menu3.add(menu3_1);

var menu4 = new MenuItem("菜单4","菜单4","#");

MenuOpr.add(menu1);
MenuOpr.add(menu2);
MenuOpr.add(menu3);
MenuOpr.add(menu4);

MenuOpr.print("main_container");

main_container为一个div层的id,这里代码就不多说了,一看就看得懂的。

当然实现这个菜单还是有不少方法的,而我这肯定不是最优的,这里我只是提供一个利用组合模式的思路而已;

如果按照常规,你可以把MenuItem都整合到Menu上,即MenuItem都改为Menu,这样的代码,你是不是更喜欢点呢?

这个就留给大家自己研究吧!

最后,说下由于文中实例的菜单样式采用li:hover的方式来弹出下级菜单,由于这个方式对于IE7以下版本是无效的,所以这里引用了cssfriendly开源项目中的两个JS文件,MenuAdapter.js和AdapterUtils.js,所以把它们放进项目中引用进来,这样该菜单就兼容了IE7以下版本的浏览器。

附:源代码下载

总结

该篇文章用Javascript设计组合模式的思路,实现一个菜单实例。

本篇到此为止,谢谢大家阅读!



参考文献:《Head First Design Pattern》


评论: 0 | 引用: 0 | 查看次数: -
发表评论
昵 称:
密 码: 游客发言不需要密码.
内 容:
验证码: 验证码
选 项:
虽然发表评论不用注册,但是为了保护您的发言权,建议您注册帐号.