什么是组合模式?
组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构,并以统一的方式处理单个对象和组合对象。
其核心思想是:单个对象(叶子节点)和组合对象(容器节点)具有相同的接口,客户端无需区分它们,只需通过统一接口操作整个树形结构。这种模式适合表示 “部分 - 整体” 关系(如文件系统中的文件与文件夹、组织架构中的员工与部门)。
组合模式的核心角色
- 抽象组件(Component):定义叶子节点和容器节点的公共接口,声明对单个对象和组合对象的统一操作(如添加、删除、遍历)。
- 叶子节点(Leaf):表示树形结构中的最小单元(单个对象),实现抽象组件的接口,但不包含子节点(因此添加 / 删除子节点的方法可能为空或抛出错误)。
- 容器节点(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
代码说明
-
抽象组件(FileSystemNode):定义了文件系统节点的公共接口(
getSize、display、add、remove),确保叶子节点(文件)和容器节点(文件夹)具有一致的操作方式。 -
叶子节点(File):
- 表示单个文件,
getSize返回自身大小,display显示文件名和大小。 - 不支持
add和remove方法(继承父类的默认实现,抛出错误),因为文件不能包含子节点。
- 表示单个文件,
-
容器节点(Folder):
- 表示文件夹,通过
children数组存储子节点(可以是File或Folder)。 getSize递归计算所有子节点的大小之和,display递归显示自身及所有子节点的结构(通过缩进体现层级)。- 实现
add和remove方法,用于管理子节点。
- 表示文件夹,通过
-
客户端:通过统一接口操作整个树形结构,无需区分是文件还是文件夹。例如,调用
rootFolder.display()可递归显示所有节点,调用getSize()可统一获取大小。
组合模式的优势
- 统一接口:客户端用相同的方式处理单个对象和组合对象,简化了代码(如示例中对
file1和rootFolder都能调用getSize)。 - 树形结构灵活扩展:可随时向容器节点中添加新的叶子或容器节点(如新增子文件夹),无需修改现有代码,符合 “开闭原则”。
- 递归操作便捷:对容器节点的操作会自动递归应用到所有子节点(如计算文件夹总大小、批量删除文件)。
常见应用场景
- 树形结构表示:如文件系统、菜单导航(多级菜单)、组织架构(公司→部门→员工)。
- 批量操作:如对所有节点执行相同操作(如批量删除、批量计算)。
- UI 组件库:如 React/Vue 中的组件树(
div包含span、button等,统一通过render方法渲染)。
组合模式的核心是 “统一单个与组合,简化树形操作”,通过抽象组件将叶子和容器统一,使客户端可以无缝处理任意复杂度的树形结构。

被折叠的 条评论
为什么被折叠?



