设计模式之组合模式

做者:葉@毛豆前端javascript

组合模式

1、定义

组合模式,将对象组合成树形结构以表示“部分-总体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具备一致性前端

在定义中提到了“部分-总体”、“单个对象”、“组合对象”这几个关键词,所以掌握组合模式的重点是要理解清楚 “部分/总体” 还有 ”单个对象“ 与 "组合对象" 的含义java

2、做用

组合模式的做用即定义描述的那样,有两个做用:git

  1. 将对象组合成树形结构,以表示“部分-总体”的层次结构
  2. 经过对象的多态性表现,使得用户对单个对象和组合对象的使用具备一致性

针对以上的两点,下面作一个详细说明,首先看一段代码:这是一个模拟电脑开机,运行应用程序的简单宏命令例子github

let runWeChat = {
    run : function() {
        console.log("wechat已经启动")
    }
}

let runQQ = {
    run : function() {
        console.log("QQ已经启动")
    }
}

let runChrome = {
    run : function() {
        console.log("Google Chrome已经启动")
    }
}

let applicationCommand = function () {
    return {
        commandLists : [],
        addCommand: function (command) {
            this.commandLists.push(command)
        },
        run: function () {
            this.commandLists.map((item, index) => {
                item.run()
            })
        }
    }
}

let ac = applicationCommand ()

ac.addCommand(runWeChat)

ac.addCommand(runQQ)

ac.addCommand(runChrome)

ac.run()
复制代码

  • 这样树形遍历的结构,经过调用组合对象的run方法,程序便会递归调用组合对象下面的子对象的run方法,所以咱们只要按下电脑的开机键,微信,qq,谷歌浏览器这些程序就会被顺序运行。这样的方式很简洁的描述出了部分-总体的结构
  • 在这个组合模式中,利用对象的多态性表现,咱们能够不关心组合对象和单个对象的区别,使用时统一使用组合结构中的全部对象,而不须要关心它是组合对象仍是单个对象

这样的方式在实际的开发中能够带来很大的便利性。当咱们在开机程序中添加命令时,无需关心这个是宏命令仍是子命令,只关心这个命令是有能够执行的run方法,那么这个命令就能够被添加。执行的差别是在代码里体现的,对客户无感。浏览器

仍以上面的宏命令为例,请求在树最顶端的对象往下传递,若是此时处理请求的是普通子命令(叶子对象),这个时候叶子对象本身就会对请求作出处理;若此时处理请求的宏命令(组合对象),组合对象则遍历它的子对象,将请求传递下去。上面的例子只是简单的组合对象下只有叶子对象,叶子对象是树的最小单位,不会再有其余子节点,组合对象下可能还有子节点,以下图:微信

请求是由上至下沿着树进行传递,直到叶子对象,做为使用者,只须要关心树最顶层的组合对象,只要请求这个组合对象,请求便会沿着树往下传递,顺次到达全部的叶子对象app

3、组合模式例子-员工部门关系

在公司里,必然存在员工的部门以及从属关系(这里只考虑每一个人只属于一个部门),公司分为众多的部门,每一个部门会有一个主管,带领下面的员工,这个员工可能只是最末端的一个职位,也有可能在他手下还有一波兄弟,每一个部门是一个组合对象,部门下面的小组仍是一个组合对象,这样就给构成了组合的模式,接下来咱们来看下例子:性能

let leaderStaff = function (name, sex, apartment) {
    this.name = name;
    this.sex = sex;
    this.apartment = apartment;
    this.children = [];
}
leaderStaff.prototype.add = function (child) {
    this.children.push(child)
}
leaderStaff.prototype.doCount = function () {
    console.log("leader", this.name +"--"+ this.sex +"--"+ this.apartment)
    this.children.map((child, index) => {
        child.doCount()
    })
}   
let staff = function(name, sex, apartment) {
    this.name = name;
    this.sex = sex;
    this.apartment = apartment;
}
staff.prototype.add = function() {
    throw new Error("我还只是个孩子!!->_->")
}
staff.prototype.doCount = function () {
    console.log("普通员工",this.name +"--"+ this.sex +"--"+ this.apartment)
}
let leaderStaff1 = new leaderStaff('大旺', '男', 'A')
let leaderStaff2 = new leaderStaff('大张', '男', 'B')
let leaderStaff3 = new leaderStaff('大李', '男', 'C')
let leaderStaff4 = new leaderStaff('大无', '女', 'D')
let staff5 = new staff('小马', '', '0') 
let staff1 = new staff('小黑', '男', 'd')
let staff2 = new staff('小撒', '女', 'e')
let staff3 = new staff('小周', '女', 'f')
let staff4 = new staff('小郑', '男', 'g')

leaderStaff1.add(leaderStaff3);
leaderStaff1.add(leaderStaff2);
leaderStaff1.add(leaderStaff4)
leaderStaff1.add(staff1)

leaderStaff3.add(staff4)
leaderStaff3.add(staff3)
leaderStaff4.add(staff2)
// staff3.add(staff5)

leaderStaff1.doCount()
复制代码

执行结果如图:ui

这样看起来彷佛树形结构不是很明显,因此咱们再看下中间执行的数据:

这里咱们把拥有下级(子元素)的都统称为老板(leaderStaff),没有下级(子元素)的都叫作员工(staff),员工下面在没有子元素,不然抛出错误。他们都有doCount这个方法。很明显leaderStaff1(大旺)有四个子元素,其中大李又有两个子元素,以及大无有一个子元素,这些都是被称做组合对象,使用的人也不须要分辨他是孩子对象仍是祖先元素。

这里咱们改变树的结构,增长新的数据,却不用修改原来的代码,这是可取的,符合开放-封闭原则

4、注意事项

咱们如今了解了组合模式,还要说下在使用组合模式的实收,有一些值得咱们注意的地方:

  1. 组合模式不是父子关系 组合模式的树形结构容易让人误觉得组合对象和叶子对象之间是父子关系,但并非这样的。组合模式是一种HAS-A(聚合)的关系,而不是IS-A,组合对象把请求委托给它所包的全部叶对象,它们可以合做的关键是拥有相同是的接口

  2. 对叶对象操做的一致性 组合模式除了要求组合对象和也对象拥有相同的接口以外,还有一个必要条件,就是对一组也对象的操做必须具备一致性

  3. 双向映射关系 上面的例子中,公司给员工发送通知的步骤是从公司到各个部分,再到小组,最后才到每一个员工的邮箱里,这即是一个组合模式的例子,可是存在特殊状况,一个员工同属于多个部门的时候就没有了严格意义上的层次结构,在这种状况下便再也不适用组合模式,不然该员工极可能收到两份邮件

  4. 用职责链模式提升组合模式性能 一些状况下,生成树的结构十分复杂,节点数量众多,在遍历的过程当中,消耗性能。职责链模式就是在遍历的过程当中,只让请求顺着联调从父元素往子元素上传递,或子元素往父元素身上船体,知道遇到能处理请求的对象未知,避免浪费

5、总结

虽然多数时候组,合模式带给咱们便利,让咱们可使用树形的方式去建立对象结构,还能够忽略组合对象和单个对象之间的差异,使用一致的方式处理。可是,它的缺点也须要注意:他们的区别只有在运行的时候才会体现出来。

相关文章
相关标签/搜索