深入理解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 相关文章推荐
使一个函数作为另外一个函数的参数来运行的javascript代码
Aug 13 Javascript
javascript温习的一些笔记 基础常用知识小结
Jun 22 Javascript
让input框实现类似百度的搜索提示(基于jquery事件监听)
Jan 31 Javascript
jquery提示效果实例分析
Nov 25 Javascript
实现前后端数据交互方法汇总
Apr 07 Javascript
基于JavaScript实现弹出框效果
Feb 19 Javascript
微信小程序 rpx 尺寸单位详细介绍
Oct 13 Javascript
简单三步实现报表页面集成天气
Dec 15 Javascript
微信小程序实战之自定义抽屉菜单(7)
Apr 18 Javascript
详解vue-cli本地环境API代理设置和解决跨域
Sep 05 Javascript
详解基于vue-cli3.0如何构建功能完善的前端架子
Oct 09 Javascript
vue项目强制清除页面缓存的例子
Nov 06 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
超神学院:鹤熙已踏入神圣领域,实力不比凯莎弱
2020/03/02 国漫
PHPMailer发送HTML内容、带附件的邮件实例
2014/07/01 PHP
Zend Framework教程之视图组件Zend_View用法详解
2016/03/05 PHP
js的参数有长度限制吗?发现不能超过2083个字符
2014/04/20 Javascript
Jquery 实现table样式的设定
2015/01/28 Javascript
js实现同一个页面多个渐变效果的方法
2015/04/10 Javascript
浅谈jQuery中replace()方法
2015/05/13 Javascript
Jquery使用css方法改变样式实例
2015/05/18 Javascript
Js 获取当前函数参数对象的实现代码
2016/06/20 Javascript
javascript动画之模拟拖拽效果篇
2016/09/26 Javascript
微信小程序商城项目之淘宝分类入口(2)
2017/04/17 Javascript
Vue中使用方法、计算属性或观察者的方法实例详解
2018/10/31 Javascript
webpack打包html里面img后src为“[object Module]”问题
2019/12/22 Javascript
vue 实现用户登录方式的切换功能
2020/04/14 Javascript
JavaScript或jQuery 获取option value值方法解析
2020/05/12 jQuery
Vue中ref和$refs的介绍以及使用方法示例
2021/01/11 Vue.js
在nodejs中创建child process的方法
2021/01/26 NodeJs
[12:29]《一刀刀一天》之DOTA全时刻19:蝙蝠骑士田伯光再度不举
2014/06/10 DOTA
Python的批量远程管理和部署工具Fabric用法实例
2015/01/23 Python
Python处理json字符串转化为字典的简单实现
2016/07/07 Python
Python中super函数的用法
2017/11/17 Python
pandas dataframe添加表格框线输出的方法
2019/02/08 Python
python的pstuil模块使用方法总结
2019/07/26 Python
Win下PyInstaller 安装和使用教程
2019/12/25 Python
Python3标准库之threading进程中管理并发操作方法
2020/03/30 Python
Opencv常见图像格式Data Type及代码实例
2020/11/02 Python
Pandas数据分析的一些常用小技巧
2021/02/07 Python
canvas 基础之图像处理的使用
2020/04/10 HTML / CSS
Mytheresa英国官网:拥有160多个奢侈品品牌
2016/10/09 全球购物
《望洞庭》教学反思
2014/02/16 职场文书
篝火晚会主持词
2014/03/25 职场文书
战略性融资合作协议书范本
2014/10/17 职场文书
践行三严三实心得体会(2016推荐篇)
2016/01/06 职场文书
Go timer如何调度
2021/06/09 Golang
maven依赖的version声明控制方式
2022/01/18 Java/Android
Android 中的类文件和类加载器详情
2022/06/05 Java/Android