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

介绍

组合模式(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接口。

(0)

相关推荐

  • JavaScript 设计模式之组合模式解析

    怎么说呢?!就像是动物(组合对象)一样,当它生下后代(叶对象)时,它的后代就有了某种功能(比如:挖洞,听力好等等):也像是一棵树,它有一个根(组合对象)然后是从这个棵树向外冒出的其他枝杆(组合对象)以及从这些枝杆又向外长的叶子(叶对象).换句话说,就是当祖先已经有了,那么只要从这个祖先衍生出来的其他孩子(包括这个祖先下的其他组合对象)已经就具备了某种功能,看上去貌似又有些像是继承."组合模式"在组合对象的层次体系中有两种类型的对象:叶对象和组合对象.组合模式擅长于对大批对象进行操作.

  • 设计模式中的组合模式在JavaScript程序构建中的使用

    定义 组合,顾名思义是指用包含多个部件的对象创建单一实体. 这个单一实体将用作所有这些部件的访问点,虽然这大大简化了操作,但也可能具有相当的欺骗性,因为没有哪种隐性方式明确表明该组合包含多少部件. 组合模式的目标是解耦客户程序与复杂元素内部架构,使得客户程序对待所有子元素都一视同仁. 每个子节点都可以使复杂的存在,对于父节点来说,不需要知道子节点的复杂性或者实现子节点的复杂性,只需要关注子节点的特定方法,便可以使用子节点.简化了父和子之间的关系. 对于子节点来说也是一样的,过多的接口暴露有时候也

  • JavaScript组合模式学习要点

    组合模式:将一组对象组合成树形结构,并统一对待组合对象和叶对象,忽略它们之间的不同(因为叶对象也可以也可以包含叶对象而成为组合对象),组合模式中的对象只能是一对多的关系,不能出现多对一. 基本单元:一个组合对象包含多个叶对象.每个基本单元又可以是别的组合对象的叶对象类似文件夹与它里面的内容的关系网,一个文件夹或文件又可以是其它文件夹的内容,但一个文件夹或文件不能同时属于多个上级文件夹. 在JavaScript中实现组合模式时,要保证组合对象和叶对象拥有相同的接口方法,对同一组叶对象的操作必须具有

  • JavaScript设计模式开发中组合模式的使用教程

    我们平时开发过程中,一定会遇到这种情况:同时处理简单对象和由简单对象组成的复杂对象,这些简单对象和复杂对象会组合成树形结构,在客户端对其处理的时候要保持一致性.比如电商网站中的产品订单,每一张产品订单可能有多个子订单组合,比如操作系统的文件夹,每个文件夹有多个子文件夹或文件,我们作为用户对其进行复制,删除等操作时,不管是文件夹还是文件,对我们操作者来说是一样的.在这种场景下,就非常适合使用组合模式来实现. 基本知识 组合模式:将对象组合成树形结构以表示"部分-整体"的层次结构,组合模式

  • Java结构型设计模式之组合模式详解

    目录 组合模式 应用场景 优缺点 主要角色 组合模式结构 分类 透明组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 安全组合模式 创建抽象根节点 创建树枝节点 创建叶子节点 客户端调用 组合模式 组合模式(Composite Pattern)也称为整体-部分(Part-Whole)模式,属于结构型模式. 它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户端对单个对象和组合对象的使用具有一致性. 组合模式一般用来描述整体与部分的关系,它将对象

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

    介绍 组合模式(Composite)将对象组合成树形结构以表示"部分-整体"的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性. 常见的场景有asp.net里的控件机制(即control里可以包含子control,可以递归操作.添加.删除子control),类似的还有DOM的机制,一个DOM节点可以包含子节点,不管是父节点还是子节点都有添加.删除.遍历子节点的通用功能.所以说组合模式的关键是要有一个抽象类,它既可以表示子元素,又可以表示父元素. 正文 举个例子,有家餐厅提

  • Java设计模式之原型模式详解

    一.前言 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式.在实际应用中,原型模式很少单独出现.经常与其他模式混用,他的原型类Prototype也常用抽象类来替代. 该模式的思想就是将一个对象作为原型,对其进行复制.克隆,产生一个和原对象类似的新对象.在Java中,复制对象是通过clone()实现的,先创建一个原型类,通过实现Cloneable 接口 public class Prototype implements Cloneable { public

  • Java行为型设计模式之策略模式详解

    目录 1.策略设计模式定义 2.策略设计模式的有点与不足 3.策略设计模式的实现思路 4.代码示例 5.策略设计模式的应用场景 编程是一门艺术,大批量的改动显然是非常丑陋的做法,用心的琢磨写的代码让它变的更美观. 在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如超市促销可以釆用打折.送商品.送积分等方法. 在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有快速排序.归并排序.选

  • Java设计模式之命令模式详解

    命令模式 定义:将请求封装成对象,这可以让你使用不同的请求.队列.或者日志来参数化其他对象. 何时使用命令模式?当需要将发出请求的对象和执行请求的对象解耦的时候,使用命令模式. 在被解耦的两者之间是通过命令对象进行沟通的.命令对象封装了接收者和一个或一组动作. 调用者通过调用命令对象的execute()方法发出请求,这会使接收者的动作被调用. 调用者可以接收命令当作参数,甚至在运行时动态地进行. 优点: 1.降低了系统耦合度. 2.新的命令可以很容易添加到系统中去. 缺点:使用命令模式可能会导致

  • Java设计模式之代理模式详解

    一.代理模式 代理模式就是有一个张三,别人都没有办法找到他,只有他的秘书可以找到他.那其他人想和张三交互,只能通过他的秘书来进行转达交互.这个秘书就是代理者,他代理张三. 再看看另一个例子:卖房子 卖房子的步骤: 1.找买家 2.谈价钱 3.签合同 4.和房产局签订一些乱七八糟转让协议 一般卖家只在签合同的时候可能出面一下,其他的1,2,4都由中介去做.那你问这样有什么用呢? 首先,一个中介可以代理多个卖房子的卖家,其次,我们可以在不修改卖家的代码的情况下,给他实现房子加价.打广告等等夹带私货的

  • 深入理解JavaScript系列(3) 全面解析Module模式

    简介 Module模式是JavaScript编程中一个非常通用的模式,一般情况下,大家都知道基本用法,本文尝试着给大家更多该模式的高级使用方式. 首先我们来看看Module模式的基本特征: 模块化,可重用 封装了变量和function,和全局的namaspace不接触,松耦合 只暴露可用public的方法,其它私有方法全部隐藏 关于Module模式,最早是由YUI的成员Eric Miraglia在4年前提出了这个概念,我们将从一个简单的例子来解释一下基本的用法(如果你已经非常熟悉了,请忽略这一节

  • Java设计模式之java组合模式详解

    目录 引言 组合模式介绍 角色 模式结构 示例代码 水果盘 文件浏览 更复杂的组合模式 透明与安全 透明组合模式 安全组合模式 组合模式总结 优点 缺点 适用场景 应用 XML文档解析 文件 HashMap Mybatis SqlNode中的组合模式 参考文章 总结 引言 树形结构不论在生活中或者是开发中都是一种非常常见的结构,一个容器对象(如文件夹)下可以存放多种不同的叶子对象或者容器对象,容器对象与叶子对象之间属性差别可能非常大. 由于容器对象和叶子对象在功能上的区别,在使用这些对象的代码中

  • 《javascript设计模式》学习笔记七:Javascript面向对象程序设计组合模式详解

    本文实例讲述了Javascript面向对象程序设计组合模式.分享给大家供大家参考,具体如下: 概述 关于组合模式的定义:组合模式(Composite Pattern)有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦.来自百度百科:http://baike.baidu.com/view/3591789.htm 其实从面向对象之五之后,与javascript本身关系不是很大,更

  • javascript设计模式之解释器模式详解

    神马是"解释器模式"? 先翻开<GOF>看看Definition:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子. 在开篇之前还是要科普几个概念: 抽象语法树: 解释器模式并未解释如何创建一个抽象语法树.它不涉及语法分析.抽象语法树可用一个表驱动的语法分析程序来完成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接client提供. 解析器: 指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序. 解

随机推荐