JavaScript设计模式-组合模式

概念

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

应用

虚拟DOM

  Vue、React用render函数渲染虚拟DOM。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        div{
            border:1px solid red;
        }
        p{
            border:1px solid blue;
        }
    </style>
</head>
<body>
<div id="root"></div>
<script>
    let vnode = {
        type:'div',
        attrs:{
            id:'div2',
            className:'container'
        },
        children:[
            {
                type:'p',
                attrs:{},
                children:['123']
            },
            {
                type:'p',
                attrs:{},
                children:['456']
            }
        ]
    }
    function render(vnode,parent){
        let element;
        if(typeof vnode == 'object'){
            let {type,attrs,children=[]} = vnode;
            element = document.createElement(type);
            for(let attr in attrs){
                if(attr == 'className'){
                    element.setAttribute('class',attrs[attr]);
                }else{
                    element.setAttribute(attr,attrs[attr]);
                }
            }
            for(let child of children){
                render(child,element);
            }
        }else{
            element = document.createTextNode(vnode);
        }
        parent.appendChild(element);
    }
    render(vnode,document.getElementById('root'));
</script>
</body>
</html>

宏命令

  宏命令对象包含了一组具体的子命令对象,不管是宏命令对象,还是子命令对象,都有一个execute方法负责执行命令。

let closeDoorCommand = {
    execute: function(){
        console.log( '关门' )
    }
}
let openPcCommand = {
    execute: function(){
        console.log( '开电脑' )
    }
}
let openQQCommand = {
    execute: function(){
        console.log( '登录QQ' )
    }
}
let MacroCommand = function(){
    return {
        commandsList: [],
        add: function( command ){
            this.commandsList.push( command )
        },
        execute: function(){
            for ( let i = 0, command; command = this.commandsList[ i++ ]; ){
                command.execute()
            }
        }
    }
}
let macroCommand = MacroCommand()
macroCommand.add( closeDoorCommand )
macroCommand.add( openPcCommand )
macroCommand.add( openQQCommand )
macroCommand.execute()

  宏命令中包含了一组子命令,它们组成了一个树形结构,这里是一棵结构非常简单的树。



  其中,marcoCommand被称为组合对象,closeDoorCommand、openPcCommand、openQQCommand都是子对象。在macroCommand的execute方法里,并不执行真正的操作,而是遍历它所包含的子对象,把真正的execute请求委托给这些子对象。macroCommand表现得像一个命令,但它实际上只是一组真正命令的“代理”。并非真正的代理,虽然结构上相似,但macroCommand只负责传递请求给子对象,它的目的不在于控制对子对象的访问。
  组合模式将对象组合成树形结构,以表示“部分——整体”的层次结构。除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
  1、表示树形结构。组合模式的优点:提供了一种遍历树形结构的方案,通过调用组合对象的execute方法,程序会递归调用组合对象下面的子对象的execute方法。组合模式可以非常方便地描述对象部分——整体层次结构。
  2、利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。

扫描文件夹

  文件夹和文件之间的关系,非常适合用组合模式来描述。文件夹里既可以包含文件,又可以包含其他文件夹,最终可能组合成一棵树,组合模式在文件夹的应用中有以下两层好处。
  1. 组合模式让Ctrl+V、Ctrl+C成为了一个统一的操作。例如,在移动硬盘里找到了一些电子书,想把它们复制到F盘中的学习资料文件夹。在复制这些电子书的时候,并不需要考虑这批文件的类型,不管它们是单独的电子书还是被放在了文件夹中。
  2. 用杀毒软件扫描该文件夹时,往往不会关心里面有多少文件和子文件夹,组合模式使得只需要操作最外层的文件夹进行扫描。
  现在来编写代码,首先分别定义好文件夹Folder和文件File这两个类。见如下代码:

let Folder = function( name ){
    this.name = name
    this.files = []
}
Folder.prototype.add = function( file ){
    this.files.push( file )
}
Folder.prototype.scan = function(){
    console.log( '开始扫描文件夹: ' + this.name )
    for ( let i = 0, file, files = this.files; file = files[ i++ ]; ){
        file.scan()
    }
}
let File = function( name ){
    this.name = name
}
File.prototype.add = function(){
    throw new Error( '文件下面不能再添加文件' )
}
File.prototype.scan = function(){
    console.log( '开始扫描文件: ' + this.name )
}

  接下来创建一些文件夹和文件对象,并且让它们组合成一棵树,这棵树就是F盘里的现有文件目录结构。

let folder = new Folder( '学习资料' )
let folder1 = new Folder( 'JavaScript' )
let folder2 = new Folder ( 'jQuery' )
let file1 = new File( 'JavaScript 设计模式与开发实践' )
let file2 = new File( '精通jQuery' )
let file3 = new File( '重构与模式' )
folder1.add( file1 )
folder2.add( file2 )
folder.add( folder1 )
folder.add( folder2 )
folder.add( file3 )

  现在的需求是把移动硬盘里的文件和文件夹都复制到这棵树中,假设已经得到了这些文件对象:

let folder3 = new Folder( 'Nodejs' )
let file4 = new File( '深入浅出Node.js' )
folder3.add( file4 )
let file5 = new File( 'JavaScript 语言精髓与编程实践' )

  接下来就是把这些文件都添加到原有的树中:

folder.add( folder3 )
folder.add( file5 )

  通过这个例子,再次看到客户是如何同等对待组合对象和基本对象的。在添加一批文件的操作过程中,客户不用分辨它们到底是文件还是文件夹。新增加的文件和文件夹能够很容易地添加到原来的树结构中,和树里已有的对象一起工作。改变了树的结构,增加了新的数据,却不用修改任何一句原有的代码,这是符合开放——封闭原则的
  运用了组合模式之后,扫描整个文件夹的操作也是轻而易举的,只需要操作树的最顶端对象:

folder.scan()

总结

  组合模式如果运用得当,可以大大简化客户的代码。一般来说,组合模式适用于以下这两种情况。
  1. 表示对象的部分——整体层次结构。组合模式可以方便地构造一棵树来表示对象的部分——整体结构。特别是在开发期间不确定这棵树到底存在多少层次的时候。在树的构造最终完成之后,只需要通过请求树的最顶层对象,便能对整棵树做统一的操作。在组合模式中增加和删除树的节点非常方便,并且符合开放——封闭原则。
  2. 客户希望统一对待树中的所有对象。组合模式使客户可以忽略组合对象和子对象的区别,客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是子对象,也就不用写一堆if、else语句来分别处理它们。组合对象和子对象会各自做自己正确的事情,这是组合模式最重要的能力。
  然而,组合模式并不是完美的,它可能会产生一个这样的系统:系统中的每个对象看起来都与其他对象差不多。它们的区别只有在运行的时候会才会显现出来,这会使代码难以理解。此外,如果通过组合模式创建了太多的对象,那么这些对象可能会让系统负担不起。

参考文献

《JavaScript设计模式与开发实践》
《大话设计模式》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,734评论 0 14
  • javascript设计模式与开发实践 设计模式 每个设计模式我们需要从三点问题入手: 定义 作用 用法与实现 单...
    穿牛仔裤的蚊子阅读 4,032评论 0 13
  • 高阶函数是指至少满足下列条件之一的函数: 1) 函数可以作为参数被传递2) 函数可以作为参数输出。 AOP: 1)...
    world_7735阅读 276评论 0 1
  • 临近岁末,却惊闻噩耗。久未谋面的小邵阿姨因肺癌突然离世。阿姨四十出头,眉清目秀,善良端庄。因为是父辈的故交,才被没...
    伽墨阅读 674评论 0 0
  • 作者/胄宁 愿你今后的日子啊, 清澈似水, 明朗如画, 不管遇到什么困难, 都能大吃一顿, 大睡一觉, 第二天以最...
    胄宁阅读 293评论 0 3