组合模式
用树形结构表示部分-整体关系,统一处理单个对象和组合对象
问题
什么是组合模式?如何在前端中实现和应用?
解答
组合模式将对象组合成树形结构,让客户端可以用统一的方式处理单个对象和组合对象。
基础实现:文件系统
// 抽象组件 - 定义统一接口
class FileSystemNode {
constructor(name) {
this.name = name;
}
getSize() {
throw new Error('子类必须实现 getSize 方法');
}
print(indent = '') {
throw new Error('子类必须实现 print 方法');
}
}
// 叶子节点 - 文件
class File extends FileSystemNode {
constructor(name, size) {
super(name);
this.size = size;
}
getSize() {
return this.size;
}
print(indent = '') {
console.log(`${indent}📄 ${this.name} (${this.size}KB)`);
}
}
// 组合节点 - 文件夹
class Folder extends FileSystemNode {
constructor(name) {
super(name);
this.children = [];
}
add(node) {
this.children.push(node);
return this;
}
remove(node) {
const index = this.children.indexOf(node);
if (index > -1) {
this.children.splice(index, 1);
}
return this;
}
// 递归计算所有子节点大小
getSize() {
return this.children.reduce((total, child) => total + child.getSize(), 0);
}
print(indent = '') {
console.log(`${indent}📁 ${this.name} (${this.getSize()}KB)`);
this.children.forEach(child => child.print(indent + ' '));
}
}
// 使用示例
const root = new Folder('项目');
const src = new Folder('src');
const components = new Folder('components');
components.add(new File('Button.jsx', 5));
components.add(new File('Modal.jsx', 8));
src.add(components);
src.add(new File('index.js', 2));
src.add(new File('App.jsx', 10));
root.add(src);
root.add(new File('package.json', 1));
root.add(new File('README.md', 3));
root.print();
// 📁 项目 (29KB)
// 📁 src (25KB)
// 📁 components (13KB)
// 📄 Button.jsx (5KB)
// 📄 Modal.jsx (8KB)
// 📄 index.js (2KB)
// 📄 App.jsx (10KB)
// 📄 package.json (1KB)
// 📄 README.md (3KB
console.log('总大小:', root.getSize() + 'KB'); // 29KB
实际应用:菜单组件
// 菜单项(叶子)
class MenuItem {
constructor(name, action) {
this.name = name;
this.action = action;
}
render() {
const li = document.createElement('li');
li.textContent = this.name;
li.onclick = this.action;
li.className = 'menu-item';
return li;
}
}
// 子菜单(组合)
class SubMenu {
constructor(name) {
this.name = name;
this.children = [];
}
add(item) {
this.children.push(item);
return this;
}
render() {
const li = document.createElement('li');
li.className = 'submenu';
const span = document.createElement('span');
span.textContent = this.name + ' ▸';
li.appendChild(span);
const ul = document.createElement('ul');
this.children.forEach(child => ul.appendChild(child.render()));
li.appendChild(ul);
return li;
}
}
// 构建菜单
const fileMenu = new SubMenu('文件')
.add(new MenuItem('新建', () => console.log('新建文件')))
.add(new MenuItem('打开', () => console.log('打开文件')))
.add(new SubMenu('最近打开')
.add(new MenuItem('project1.js', () => {}))
.add(new MenuItem('project2.js', () => {}))
);
const editMenu = new SubMenu('编辑')
.add(new MenuItem('撤销', () => console.log('撤销')))
.add(new MenuItem('重做', () => console.log('重做')));
// 渲染到页面
const nav = document.createElement('ul');
nav.className = 'menu';
nav.appendChild(fileMenu.render());
nav.appendChild(editMenu.render());
简化版:函数式实现
// 用对象字面量表示树结构
const menuConfig = {
type: 'menu',
children: [
{
type: 'submenu',
name: '文件',
children: [
{ type: 'item', name: '新建', action: 'create' },
{ type: 'item', name: '保存', action: 'save' },
]
},
{ type: 'item', name: '帮助', action: 'help' }
]
};
// 递归渲染
function renderMenu(node) {
if (node.type === 'item') {
return `<li class="item">${node.name}</li>`;
}
if (node.type === 'submenu') {
const children = node.children.map(renderMenu).join('');
return `<li class="submenu"><span>${node.name}</span><ul>${children}</ul></li>`;
}
if (node.type === 'menu') {
const children = node.children.map(renderMenu).join('');
return `<ul class="menu">${children}</ul>`;
}
}
console.log(renderMenu(menuConfig));
关键点
- 统一接口:叶子节点和组合节点实现相同的接口,客户端无需区分
- 递归结构:组合节点包含子节点,形成树形结构
- 透明性:对单个对象和组合对象的操作一致
- 典型场景:文件系统、DOM 树、菜单导航、组织架构
- React 中的体现:组件嵌套、children prop 就是组合模式的应用
目录