深入理解JavaScript系列(40):设计模式之组合模式详解


Posted in Javascript onMarch 04, 2015

介绍

组合模式(Composite)将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。

常见的场景有asp.net里的控件机制(即control里可以包含子control,可以递归操作、添加、删除子control),类似的还有DOM的机制,一个DOM节点可以包含子节点,不管是父节点还是子节点都有添加、删除、遍历子节点的通用功能。所以说组合模式的关键是要有一个抽象类,它既可以表示子元素,又可以表示父元素。

正文

举个例子,有家餐厅提供了各种各样的菜品,每个餐桌都有一本菜单,菜单上列出了该餐厅所偶的菜品,有早餐糕点、午餐、晚餐等等,每个餐都有各种各样的菜单项,假设不管是菜单项还是整个菜单都应该是可以打印的,而且可以添加子项,比如午餐可以添加新菜品,而菜单项咖啡也可以添加糖啊什么的。

这种情况,我们就可以利用组合的方式将这些内容表示为层次结构了。我们来逐一分解一下我们的实现步骤。

第一步,先实现我们的“抽象类”函数MenuComponent:

var MenuComponent = function () {

};

MenuComponent.prototype.getName = function () {

    throw new Error("该方法必须重写!");

};

MenuComponent.prototype.getDescription = function () {

    throw new Error("该方法必须重写!");

};

MenuComponent.prototype.getPrice = function () {

    throw new Error("该方法必须重写!");

};

MenuComponent.prototype.isVegetarian = function () {

    throw new Error("该方法必须重写!");

};

MenuComponent.prototype.print = function () {

    throw new Error("该方法必须重写!");

};

MenuComponent.prototype.add = function () {

    throw new Error("该方法必须重写!");

};

MenuComponent.prototype.remove = function () {

    throw new Error("该方法必须重写!");

};

MenuComponent.prototype.getChild = function () {

    throw new Error("该方法必须重写!");

};

该函数提供了2种类型的方法,一种是获取信息的,比如价格,名称等,另外一种是通用操作方法,比如打印、添加、删除、获取子菜单。

第二步,创建基本的菜品项:

var MenuItem = function (sName, sDescription, bVegetarian, nPrice) {

    MenuComponent.apply(this);

    this.sName = sName;

    this.sDescription = sDescription;

    this.bVegetarian = bVegetarian;

    this.nPrice = nPrice;

};

MenuItem.prototype = new MenuComponent();

MenuItem.prototype.getName = function () {

    return this.sName;

};

MenuItem.prototype.getDescription = function () {

    return this.sDescription;

};

MenuItem.prototype.getPrice = function () {

    return this.nPrice;

};

MenuItem.prototype.isVegetarian = function () {

    return this.bVegetarian;

};

MenuItem.prototype.print = function () {

    console.log(this.getName() + ": " + this.getDescription() + ", " + this.getPrice() + "euros");

};

由代码可以看出,我们只重新了原型的4个获取信息的方法和print方法,没有重载其它3个操作方法,因为基本菜品不包含添加、删除、获取子菜品的方式。

第三步,创建菜品:

var Menu = function (sName, sDescription) {

    MenuComponent.apply(this);

    this.aMenuComponents = [];

    this.sName = sName;

    this.sDescription = sDescription;

    this.createIterator = function () {

        throw new Error("This method must be overwritten!");

    };

};

Menu.prototype = new MenuComponent();

Menu.prototype.add = function (oMenuComponent) {

    // 添加子菜品

    this.aMenuComponents.push(oMenuComponent);

};

Menu.prototype.remove = function (oMenuComponent) {

    // 删除子菜品

    var aMenuItems = [];

    var nMenuItem = 0;

    var nLenMenuItems = this.aMenuComponents.length;

    var oItem = null;
    for (; nMenuItem < nLenMenuItems; ) {

        oItem = this.aMenuComponents[nMenuItem];

        if (oItem !== oMenuComponent) {

            aMenuItems.push(oItem);

        }

        nMenuItem = nMenuItem + 1;

    }

    this.aMenuComponents = aMenuItems;

};

Menu.prototype.getChild = function (nIndex) {

    //获取指定的子菜品

    return this.aMenuComponents[nIndex];

};

Menu.prototype.getName = function () {

    return this.sName;

};

Menu.prototype.getDescription = function () {

    return this.sDescription;

};

Menu.prototype.print = function () {

    // 打印当前菜品以及所有的子菜品

    console.log(this.getName() + ": " + this.getDescription());

    console.log("--------------------------------------------");
    var nMenuComponent = 0;

    var nLenMenuComponents = this.aMenuComponents.length;

    var oMenuComponent = null;
    for (; nMenuComponent < nLenMenuComponents; ) {

        oMenuComponent = this.aMenuComponents[nMenuComponent];

        oMenuComponent.print();

        nMenuComponent = nMenuComponent + 1;

    }

};

注意上述代码,除了实现了添加、删除、获取方法外,打印print方法是首先打印当前菜品信息,然后循环遍历打印所有子菜品信息。

第四步,创建指定的菜品:

我们可以创建几个真实的菜品,比如晚餐、咖啡、糕点等等,其都是用Menu作为其原型,代码如下:

var DinnerMenu = function () {

    Menu.apply(this);

};

DinnerMenu.prototype = new Menu();
var CafeMenu = function () {

    Menu.apply(this);

};

CafeMenu.prototype = new Menu();
var PancakeHouseMenu = function () {

    Menu.apply(this);

};

PancakeHouseMenu.prototype = new Menu();

第五步,创建最顶级的菜单容器——菜单本:
var Mattress = function (aMenus) {

    this.aMenus = aMenus;

};

Mattress.prototype.printMenu = function () {

    this.aMenus.print();

};

该函数接收一个菜单数组作为参数,并且值提供了printMenu方法用于打印所有的菜单内容。

第六步,调用方式:

var oPanCakeHouseMenu = new Menu("Pancake House Menu", "Breakfast");

var oDinnerMenu = new Menu("Dinner Menu", "Lunch");

var oCoffeeMenu = new Menu("Cafe Menu", "Dinner");

var oAllMenus = new Menu("ALL MENUS", "All menus combined");
oAllMenus.add(oPanCakeHouseMenu);

oAllMenus.add(oDinnerMenu);
oDinnerMenu.add(new MenuItem("Pasta", "Spaghetti with Marinara Sauce, and a slice of sourdough bread", true, 3.89));

oDinnerMenu.add(oCoffeeMenu);
oCoffeeMenu.add(new MenuItem("Express", "Coffee from machine", false, 0.99));
var oMattress = new Mattress(oAllMenus);

console.log("---------------------------------------------");

oMattress.printMenu();

console.log("---------------------------------------------");

熟悉asp.net控件开发的同学,是不是看起来很熟悉?

总结

组合模式的使用场景非常明确:

你想表示对象的部分-整体层次结构时;
你希望用户忽略组合对象和单个对象的不同,用户将统一地使用组合结构中的所有对象(方法)
另外该模式经常和装饰者一起使用,它们通常有一个公共的父类(也就是原型),因此装饰必须支持具有add、remove、getChild操作的 component接口。

Javascript 相关文章推荐
jquery.boxy插件的iframe扩展代码
Jul 02 Javascript
JavaScript版TAB选项卡效果实例
Aug 16 Javascript
浅析Node在构建超媒体API中的作用
Jul 30 Javascript
jQuery制作简洁的图片轮播效果
Apr 03 Javascript
简介JavaScript中valueOf()方法的使用
Jun 05 Javascript
jQuery实现输入框邮箱内容自动补全与上下翻动显示效果【附demo源码下载】
Sep 20 Javascript
详解win7 cmd执行vue不是内部命令的解决方法
Jul 27 Javascript
在Vue组件化中利用axios处理ajax请求的使用方法
Aug 25 Javascript
细说webpack源码之compile流程-rules参数处理技巧(2)
Dec 26 Javascript
使用svg实现动态时钟效果
Jul 17 Javascript
使用jquery模拟a标签的click事件无法实现跳转的解决
Dec 04 jQuery
Vue源码分析之Vue实例初始化详解
Aug 25 Javascript
百度地图自定义控件分享
Mar 04 #Javascript
jQuery实现仿淘宝带有指示条的图片转动切换效果完整实例
Mar 04 #Javascript
深入理解JavaScript系列(39):设计模式之适配器模式详解
Mar 04 #Javascript
深入理解JavaScript系列(38):设计模式之职责链模式详解
Mar 04 #Javascript
教你如何使用firebug调试功能了解javascript闭包和this
Mar 04 #Javascript
深入理解JavaScript系列(37):设计模式之享元模式详解
Mar 04 #Javascript
jQuery插件开发的五种形态小结
Mar 04 #Javascript
You might like
自制汽车收音机天线:收听广播的技巧和方法
2021/03/02 无线电
php数据入库前清理 注意php intval与mysql的int取值范围不同
2010/12/12 PHP
php防止SQL注入详解及防范
2013/11/12 PHP
PHP实现Snowflake生成分布式唯一ID的方法示例
2020/08/30 PHP
网站页面自动跳转实现方法PHP、JSP(上)
2010/08/01 Javascript
Google AJAX 搜索 API实现代码
2010/11/17 Javascript
js中widow.open()方法使用详解
2013/07/30 Javascript
JS下拉缓冲菜单示例代码
2013/08/30 Javascript
Jquery实现Div上下移动示例
2014/04/23 Javascript
轻松掌握JavaScript状态模式
2016/09/07 Javascript
js窗口震动小程序分享
2016/11/28 Javascript
JavaScript实现汉字转换为拼音的库文件示例
2016/12/22 Javascript
微信小程序 登录实例详解
2017/01/16 Javascript
Webpack优化配置缩小文件搜索范围
2017/12/25 Javascript
javascript、php关键字搜索函数的使用方法
2018/05/29 Javascript
IE9 elementUI文件上传的问题解决
2018/10/17 Javascript
Vue-CLI 3.X 部署项目至生产服务器的方法
2019/03/22 Javascript
微信小程序select下拉框实现效果
2019/05/15 Javascript
微信小程序云开发之模拟后台增删改查
2019/05/16 Javascript
使用异步controller与jQuery实现卷帘式分页
2019/06/18 jQuery
axios解决高并发的方法:axios.all()与axios.spread()的操作
2020/11/09 Javascript
[41:52]2018DOTA2亚洲邀请赛3月29日小组赛B组Effect VS Secret
2018/03/30 DOTA
[01:15:15]VG VS EG Supermajor小组赛B组胜者组第一轮 BO3第二场 6.2
2018/06/03 DOTA
在Python中处理字符串之isdecimal()方法的使用
2015/05/20 Python
不要用强制方法杀掉python线程
2017/02/26 Python
使用pandas实现筛选出指定列值所对应的行
2020/12/13 Python
python 制作本地应用搜索工具
2021/02/27 Python
英国最大的LED专业零售商:Led Hut
2018/03/16 全球购物
捷克原创男装和女装购物网站:Bolf.cz
2018/04/28 全球购物
Laravel的加密解密与哈希实例讲解
2021/03/24 PHP
怎样写留学自荐信
2013/11/11 职场文书
文明村创建实施方案
2014/03/27 职场文书
2014年初级职称工作总结
2014/12/08 职场文书
超搞笑婚前保证书
2015/05/08 职场文书
综治目标管理责任书
2015/05/11 职场文书
2016十一国庆节慰问信
2015/12/01 职场文书