资讯专栏INFORMATION COLUMN

JavaScript设计模式(八):组合模式

leon / 2033人阅读

摘要:不同于其它静态编程语言,实现组合模式的难点是保持树对象与叶对象之间接口保持统一,可借助定制接口规范,实现类型约束。误区规避组合不是继承,树叶对象并不是父子对象组合模式的树型结构是一种聚合的关系,而不是。

组合模式:又叫 “部分整体” 模式,将对象组合成树形结构,以表示 “部分-整体” 的层次结构。通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。

生活小栗子:文件目录,DOM 文档树

模式特点

表示 “部分-整体” 的层次结构,生成 "树叶型" 结构;

一致操作性,树叶对象对外接口保存一致(操作与数据结构一致);

自上而下的的请求流向,从树对象传递给叶对象;

调用顶层对象,会自行遍历其下的叶对象执行。

代码实现

树对象和叶对象接口统一,树对象增加一个缓存数组,存储叶对象。执行树对象方法时,将请求传递给其下叶对象执行。

// 树对象 - 文件目录
class CFolder {
    constructor(name) {
        this.name = name;
        this.files = [];
    }

    add(file) {
        this.files.push(file);
    }

    scan() {
        for (let file of this.files) {
            file.scan();
        }
    }
}

// 叶对象 - 文件
class CFile {
    constructor(name) {
        this.name = name;
    }

    add(file) {
        throw new Error("文件下面不能再添加文件");
    }

    scan() {
        console.log(`开始扫描文件:${this.name}`);
    }
}

let mediaFolder = new CFolder("娱乐");
let movieFolder = new CFolder("电影");
let musicFolder = new CFolder("音乐");

let file1 = new CFile("钢铁侠.mp4");
let file2 = new CFile("再谈记忆.mp3");
movieFolder.add(file1);
musicFolder.add(file2);
mediaFolder.add(movieFolder);
mediaFolder.add(musicFolder);
mediaFolder.scan();

/* 输出:
开始扫描文件:钢铁侠.mp4
开始扫描文件:再谈记忆.mp3
*/

CFolderCFile 接口保持一致。执行 scan() 时,若发现是树对象,则继续遍历其下的叶对象,执行 scan()

JavaScript 不同于其它静态编程语言,实现组合模式的难点是保持树对象与叶对象之间接口保持统一,可借助 TypeScript 定制接口规范,实现类型约束。

// 定义接口规范
interface Compose {
    name: string,
    add(file: CFile): void,
    scan(): void
}

// 树对象 - 文件目录
class CFolder implements Compose {
    fileList = [];
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    add(file: CFile) {
        this.fileList.push(file);
    }

    scan() {
        for (let file of this.fileList) {
            file.scan();
        }
    }
}

// 叶对象 - 文件
class CFile implements Compose {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    add(file: CFile) {
        throw new Error("文件下面不能再添加文件");
    }

    scan() {
        console.log(`开始扫描:${this.name}`)
    }
}

let mediaFolder = new CFolder("娱乐");
let movieFolder = new CFolder("电影");
let musicFolder = new CFolder("音乐");

let file1 = new CFile("钢铁侠.mp4");
let file2 = new CFile("再谈记忆.mp3");
movieFolder.add(file1);
musicFolder.add(file2);
mediaFolder.add(movieFolder);
mediaFolder.add(musicFolder);
mediaFolder.scan();

/* 输出:
开始扫描文件:钢铁侠.mp4
开始扫描文件:再谈记忆.mp3
*/
透明性的安全问题

组合模式的透明性,指的是树叶对象接口保持统一,外部调用时无需区分。但是这会带来一些问题,如上述文件目录的例子,文件(叶对象)下不可再添加文件,因此需在文件类的 add() 方法中抛出异常,以作提醒。

误区规避 1. 组合不是继承,树叶对象并不是父子对象

组合模式的树型结构是一种 HAS-A(聚合)的关系,而不是 IS-A 。树叶对象能够合作的关键,是它们对外保持统一接口,而不是叶对象继承树对象的属性方法,两者之间不是父子关系。

2. 叶对象操作保持一致性

叶对象除了与树对象接口一致外,操作也必须保持一致性。一片叶子只能生在一颗树上。调用顶层对象时,每个叶对象只能接收一次请求,一个叶对象不能从属多个树对象。

3. 叶对象实现冒泡传递

请求传递由树向叶传递,如果想逆转传递过程,需在叶对象中保留对树对象的引用,冒泡传递给树对象处理。

4. 不只是简单的子集遍历

调用对象的接口方法时,如果该对象是树对象,则会将请求传递给叶对象,由叶对象执行方法,以此类推。不同于迭代器模式,迭代器模式遍历并不会做请求传导。

应用场景

优化处理递归或分级数据结构(文件系统 - 目录文件管理);

与其它设计模式联用,如与命令模式联用实现 “宏命令”。

优缺点

优点:

忽略组合对象和单个对象的差别,对外一致接口使用;

解耦调用者与复杂元素之间的联系,处理方式变得简单。

缺点

树叶对象接口一致,无法区分,只有在运行时方可辨别;

包裹对象创建太多,额外增加内存负担。

参考文章

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

本文首发Github,期待Star!
https://github.com/ZengLingYong/blog

作者:以乐之名
本文原创,有不当的地方欢迎指出。转载请指明出处。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/110270.html

相关文章

  • JavaScript常用种继承方案

    摘要:原型式继承利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型。其中表示构造函数,一个类中只能有一个构造函数,有多个会报出错误如果没有显式指定构造方法,则会添加默认的方法,使用例子如下。 (关注福利,关注本公众号回复[资料]领取优质前端视频,包括Vue、React、Node源码和实战、面试指导)showImg(https://segmentfault.com/img/rem...

    wpw 评论0 收藏0
  • PHP设计模式)桥接模式(Bridge For PHP)

    摘要:桥接设计模式桥接模式将两个原本不相关的类结合在一起,然后利用两个类中的方法和属性,输出一份新的结果。模拟企业分组发送短信需求公司现在需要按分组临时工正式工管理层等以多种形式微博等给员工发送通知。 桥接设计模式 桥接模式:将两个原本不相关的类结合在一起,然后利用两个类中的方法和属性,输出一份新的结果。 案例 模拟毛笔 需求:现在需要准备三种粗细(大中小),并且有五种颜色的比 如果使用蜡...

    K_B_Z 评论0 收藏0
  • JavaScript学习总结()正则表达式

    摘要:首先推荐几个正则表达式编辑器正则表达式是一种查找以及字符串替换操作。此表所列的常用正则表达式,除个别外均未在前后加上任何限定,请根据需要,自行处理。例如对而言,则采用一对引号来确定正则表达式的边界。 这篇文章本来很早就要写的,拖了挺久的,现在整理下,供大家学习交流哈! 基本概念 正则表达式是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为元字符)。模式描述在搜...

    trilever 评论0 收藏0
  • Java设计模式之()——适配器模式

    摘要:适配器模式应用场景适配器模式应用场景修改已使用的接口某个已经投产中的接口需要修改,这时候使用适配器最好。适配器模式适配器模式是一种事后的补救策略。1、什么是适配器模式?Convert the interface of a class into another interface clients expect.Adapter lets classes work together that co...

    番茄西红柿 评论0 收藏2637
  • JavaScript系列--浅析原型链与继承

    摘要:综上所述有原型链继承,构造函数继承经典继承,组合继承,寄生继承,寄生组合继承五种方法,寄生组合式继承,集寄生式继承和组合继承的优点于一身是实现基于类型继承的最有效方法。 一、前言 继承是面向对象(OOP)语言中的一个最为人津津乐道的概念。许多面对对象(OOP)语言都支持两种继承方式::接口继承 和 实现继承 。 接口继承只继承方法签名,而实现继承则继承实际的方法。由于js中方法没有签名...

    draveness 评论0 收藏0

发表评论

0条评论

leon

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<