什么是组合模式?JavaScript 实现一个组合模式的示例

什么是组合模式?

组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构,并以统一的方式处理单个对象和组合对象

其核心思想是:单个对象(叶子节点)和组合对象(容器节点)具有相同的接口,客户端无需区分它们,只需通过统一接口操作整个树形结构。这种模式适合表示 “部分 - 整体” 关系(如文件系统中的文件与文件夹、组织架构中的员工与部门)。

组合模式的核心角色

  1. 抽象组件(Component):定义叶子节点和容器节点的公共接口,声明对单个对象和组合对象的统一操作(如添加、删除、遍历)。
  2. 叶子节点(Leaf):表示树形结构中的最小单元(单个对象),实现抽象组件的接口,但不包含子节点(因此添加 / 删除子节点的方法可能为空或抛出错误)。
  3. 容器节点(Composite):表示组合对象,包含子节点(可以是叶子节点或其他容器节点),实现抽象组件的接口,负责管理子节点并递归执行操作。

JavaScript 实现组合模式示例:文件系统

以下以 “文件系统” 为例,实现组合模式。场景是:文件系统由文件(叶子节点)和文件夹(容器节点)组成,文件夹可以包含文件或其他文件夹,客户端需要统一操作它们(如计算大小、显示结构)。

代码实现
// -------------- 1. 抽象组件(Component):文件系统节点 --------------
class FileSystemNode {
  constructor(name) {
    this.name = name; // 节点名称(文件名或文件夹名)
  }

  // 计算节点大小(抽象方法,由子类实现)
  getSize() {
    throw new Error("子类必须实现 getSize 方法");
  }

  // 显示节点结构(抽象方法,由子类实现)
  display(indent = 0) {
    throw new Error("子类必须实现 display 方法");
  }

  // 添加子节点(默认抛出错误,叶子节点不支持)
  add(child) {
    throw new Error("当前节点不支持添加子节点");
  }

  // 删除子节点(默认抛出错误,叶子节点不支持)
  remove(child) {
    throw new Error("当前节点不支持删除子节点");
  }
}

// -------------- 2. 叶子节点(Leaf):文件 --------------
class File extends FileSystemNode {
  constructor(name, size) {
    super(name);
    this.size = size; // 文件大小(字节)
  }

  // 实现 getSize:返回文件自身大小
  getSize() {
    return this.size;
  }

  // 实现 display:显示文件名称和大小
  display(indent = 0) {
    const spaces = "  ".repeat(indent);
    console.log(`${spaces}- ${this.name}(${this.size}B)`);
  }
}

// -------------- 3. 容器节点(Composite):文件夹 --------------
class Folder extends FileSystemNode {
  constructor(name) {
    super(name);
    this.children = []; // 存储子节点(文件或文件夹)
  }

  // 实现 getSize:递归计算所有子节点的总大小
  getSize() {
    return this.children.reduce((total, child) => total + child.getSize(), 0);
  }

  // 实现 display:显示文件夹名称,并递归显示所有子节点
  display(indent = 0) {
    const spaces = "  ".repeat(indent);
    console.log(`${spaces}+ ${this.name}(总大小:${this.getSize()}B)`);
    // 递归显示子节点,缩进+1
    this.children.forEach(child => child.display(indent + 1));
  }

  // 实现 add:添加子节点
  add(child) {
    this.children.push(child);
  }

  // 实现 remove:删除子节点
  remove(child) {
    this.children = this.children.filter(node => node !== child);
  }
}

// -------------- 4. 客户端使用 --------------
// 创建文件(叶子节点)
const file1 = new File("笔记.txt", 1024);
const file2 = new File("图片.png", 20480);
const file3 = new File("数据.csv", 5120);
const file4 = new File("代码.js", 3072);

// 创建文件夹(容器节点)
const docsFolder = new Folder("文档");
const srcFolder = new Folder("源代码");
const rootFolder = new Folder("根目录");

// 组合节点:文件夹中添加文件或子文件夹
docsFolder.add(file1);
docsFolder.add(file2);
srcFolder.add(file3);
srcFolder.add(file4);
rootFolder.add(docsFolder);
rootFolder.add(srcFolder);

// 统一操作:显示整个文件系统结构
console.log("文件系统结构:");
rootFolder.display();
// 输出:
// + 根目录(总大小:29696B)
//   + 文档(总大小:21504B)
//     - 笔记.txt(1024B)
//     - 图片.png(20480B)
//   + 源代码(总大小:8192B)
//     - 数据.csv(5120B)
//     - 代码.js(3072B)

// 统一操作:计算单个文件和文件夹的大小
console.log("\n笔记.txt 大小:", file1.getSize()); // 输出:1024
console.log("文档文件夹大小:", docsFolder.getSize()); // 输出:21504
console.log("根目录总大小:", rootFolder.getSize()); // 输出:29696

代码说明

  1. 抽象组件(FileSystemNode):定义了文件系统节点的公共接口(getSizedisplayaddremove),确保叶子节点(文件)和容器节点(文件夹)具有一致的操作方式。

  2. 叶子节点(File)

    • 表示单个文件,getSize 返回自身大小,display 显示文件名和大小。
    • 不支持 add 和 remove 方法(继承父类的默认实现,抛出错误),因为文件不能包含子节点。
  3. 容器节点(Folder)

    • 表示文件夹,通过 children 数组存储子节点(可以是 File 或 Folder)。
    • getSize 递归计算所有子节点的大小之和,display 递归显示自身及所有子节点的结构(通过缩进体现层级)。
    • 实现 add 和 remove 方法,用于管理子节点。
  4. 客户端:通过统一接口操作整个树形结构,无需区分是文件还是文件夹。例如,调用 rootFolder.display() 可递归显示所有节点,调用 getSize() 可统一获取大小。

组合模式的优势

  • 统一接口:客户端用相同的方式处理单个对象和组合对象,简化了代码(如示例中对 file1 和 rootFolder 都能调用 getSize)。
  • 树形结构灵活扩展:可随时向容器节点中添加新的叶子或容器节点(如新增子文件夹),无需修改现有代码,符合 “开闭原则”。
  • 递归操作便捷:对容器节点的操作会自动递归应用到所有子节点(如计算文件夹总大小、批量删除文件)。

常见应用场景

  • 树形结构表示:如文件系统、菜单导航(多级菜单)、组织架构(公司→部门→员工)。
  • 批量操作:如对所有节点执行相同操作(如批量删除、批量计算)。
  • UI 组件库:如 React/Vue 中的组件树(div 包含 spanbutton 等,统一通过 render 方法渲染)。

组合模式的核心是 “统一单个与组合,简化树形操作”,通过抽象组件将叶子和容器统一,使客户端可以无缝处理任意复杂度的树形结构。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值