组合模式详解:概念、实现与应用
引言
组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表示"部分-整体"的层次关系。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
什么是组合模式?
组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表示"部分-整体"的层次关系。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
核心思想
组合模式的核心思想是:
- 统一接口:为叶子节点和组合节点提供统一的接口
- 递归结构:支持对象的递归组合
- 透明性:客户端无需区分单个对象和组合对象
为什么需要组合模式?
在许多情况下,我们需要处理具有层次结构的对象:
1. 树形结构
当需要表示对象的层次结构时:
- 文件系统(文件和文件夹)
- 组织架构(员工和部门)
- 图形界面(组件和容器)
2. 统一处理
当需要统一处理单个对象和对象组合时:
- 避免类型检查
- 简化客户端代码
- 提高代码复用性
3. 递归操作
当需要对整个结构进行递归操作时:
- 计算总和或统计
- 遍历所有节点
- 应用统一的操作
组合模式的基本实现
让我们从一个简单的组合模式实现开始:
javascript
// 组件接口
class Component {
constructor(name) {
this.name = name;
}
operation() {
throw new Error('必须实现 operation 方法');
}
add(component) {
throw new Error('不支持添加操作');
}
remove(component) {
throw new Error('不支持删除操作');
}
getChild(index) {
throw new Error('不支持获取子节点操作');
}
}
// 叶子节点
class Leaf extends Component {
operation() {
return `叶子节点 ${this.name} 执行操作`;
}
}
// 组合节点
class Composite extends Component {
constructor(name) {
super(name);
this.children = [];
}
operation() {
let result = `组合节点 ${this.name} 执行操作\n`;
for (const child of this.children) {
result += ` ${child.operation()}\n`;
}
return result;
}
add(component) {
this.children.push(component);
}
remove(component) {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
}
}
getChild(index) {
return this.children[index];
}
}
// 客户端代码
function clientCode(component) {
console.log(component.operation());
}
// 使用组合模式
const root = new Composite('根节点');
const branch1 = new Composite('分支1');
const branch2 = new Composite('分支2');
const leaf1 = new Leaf('叶子1');
const leaf2 = new Leaf('叶子2');
const leaf3 = new Leaf('叶子3');
branch1.add(leaf1);
branch1.add(leaf2);
branch2.add(leaf3);
root.add(branch1);
root.add(branch2);
clientCode(root);实现要点分析
- 组件接口:定义所有组件的公共接口
- 叶子节点:表示树形结构的末端节点
- 组合节点:包含子节点的节点,可以继续添加子节点
- 客户端:通过统一接口操作所有节点
- 递归结构:支持任意层次的嵌套
组合模式的实际应用场景
1. 文件系统
在文件系统中,组合模式非常适合用于表示文件和文件夹的层次结构:
javascript
// 文件系统组件接口
class FileSystemComponent {
constructor(name) {
this.name = name;
}
getSize() {
throw new Error('必须实现 getSize 方法');
}
getName() {
return this.name;
}
getPath() {
throw new Error('必须实现 getPath 方法');
}
add(component) {
throw new Error('不支持添加操作');
}
remove(component) {
throw new Error('不支持删除操作');
}
}
// 文件(叶子节点)
class File extends FileSystemComponent {
constructor(name, size) {
super(name);
this.size = size;
}
getSize() {
return this.size;
}
getPath() {
return `/${this.name}`;
}
getInfo() {
return `文件: ${this.name}, 大小: ${this.size}KB`;
}
}
// 文件夹(组合节点)
class Folder extends FileSystemComponent {
constructor(name) {
super(name);
this.children = [];
this.parent = null;
}
getSize() {
return this.children.reduce((total, child) => total + child.getSize(), 0);
}
getPath() {
if (this.parent) {
return `${this.parent.getPath()}/${this.name}`;
}
return `/${this.name}`;
}
add(component) {
this.children.push(component);
component.parent = this;
}
remove(component) {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
component.parent = null;
}
}
getChild(index) {
return this.children[index];
}
getChildren() {
return [...this.children];
}
getInfo() {
return `文件夹: ${this.name}, 大小: ${this.getSize()}KB, 包含 ${this.children.length} 个项目`;
}
listContents(indent = 0) {
const indentStr = ' '.repeat(indent);
let result = `${indentStr}${this.getInfo()}\n`;
for (const child of this.children) {
if (child instanceof Folder) {
result += child.listContents(indent + 1);
} else {
result += `${indentStr} ${child.getInfo()}\n`;
}
}
return result;
}
search(name) {
const results = [];
// 检查当前节点
if (this.name === name) {
results.push(this);
}
// 递归搜索子节点
for (const child of this.children) {
if (child.getName() === name) {
results.push(child);
}
if (child instanceof Folder) {
results.push(...child.search(name));
}
}
return results;
}
}
// 文件系统管理器
class FileSystemManager {
constructor() {
this.root = new Folder('根目录');
}
createFile(path, size) {
const parts = path.split('/').filter(part => part !== '');
const filename = parts.pop();
let current = this.root;
// 导航到目标文件夹
for (const part of parts) {
let folder = current.getChildren().find(child => child.getName() === part && child instanceof Folder);
if (!folder) {
folder = new Folder(part);
current.add(folder);
}
current = folder;
}
// 创建文件
const file = new File(filename, size);
current.add(file);
return file;
}
createFolder(path) {
const parts = path.split('/').filter(part => part !== '');
let current = this.root;
// 创建文件夹层次结构
for (const part of parts) {
let folder = current.getChildren().find(child => child.getName() === part && child instanceof Folder);
if (!folder) {
folder = new Folder(part);
current.add(folder);
}
current = folder;
}
return current;
}
listDirectory(path = '/') {
if (path === '/') {
return this.root.listContents();
}
// 查找指定路径的文件夹
const parts = path.split('/').filter(part => part !== '');
let current = this.root;
for (const part of parts) {
const folder = current.getChildren().find(child => child.getName() === part && child instanceof Folder);
if (!folder) {
throw new Error(`路径不存在: ${path}`);
}
current = folder;
}
return current.listContents();
}
getTotalSize() {
return this.root.getSize();
}
search(name) {
return this.root.search(name);
}
}
// 使用示例
function fileSystemExample() {
const fs = new FileSystemManager();
console.log('=== 创建文件系统结构 ===');
// 创建文件夹
fs.createFolder('文档/工作');
fs.createFolder('文档/个人');
fs.createFolder('图片/风景');
fs.createFolder('图片/人物');
fs.createFolder('音乐/流行');
fs.createFolder('音乐/古典');
// 创建文件
fs.createFile('文档/工作/报告.docx', 1024);
fs.createFile('文档/工作/计划.xlsx', 512);
fs.createFile('文档/个人/日记.txt', 256);
fs.createFile('图片/风景/海滩.jpg', 2048);
fs.createFile('图片/风景/山脉.jpg', 3072);
fs.createFile('图片/人物/肖像.jpg', 1536);
fs.createFile('音乐/流行/歌曲1.mp3', 4096);
fs.createFile('音乐/古典/交响乐.mp3', 8192);
console.log('\n=== 显示文件系统结构 ===');
console.log(fs.listDirectory());
console.log('\n=== 显示特定目录 ===');
console.log(fs.listDirectory('/图片'));
console.log('\n=== 文件系统总大小 ===');
console.log(`总大小: ${fs.getTotalSize()}KB`);
console.log('\n=== 搜索文件 ===');
const results = fs.search('报告.docx');
console.log('搜索结果:');
results.forEach(result => console.log(` ${result.getPath()}`));
}
fileSystemExample();2. 图形用户界面
在GUI系统中,组合模式非常适合用于表示界面组件的层次结构:
javascript
// GUI组件接口
class GUIComponent {
constructor(name) {
this.name = name;
this.parent = null;
this.x = 0;
this.y = 0;
}
render() {
throw new Error('必须实现 render 方法');
}
setPosition(x, y) {
this.x = x;
this.y = y;
}
getPosition() {
return { x: this.x, y: this.y };
}
add(component) {
throw new Error('不支持添加操作');
}
remove(component) {
throw new Error('不支持删除操作');
}
getChild(index) {
throw new Error('不支持获取子节点操作');
}
handleEvent(event) {
// 默认事件处理
if (this.parent) {
this.parent.handleEvent(event);
}
}
}
// 基础组件(叶子节点)
class BasicComponent extends GUIComponent {
constructor(name, type) {
super(name);
this.type = type;
this.visible = true;
this.enabled = true;
}
render() {
if (!this.visible) return '';
return `${this.type} "${this.name}" at (${this.x}, ${this.y})`;
}
setVisible(visible) {
this.visible = visible;
}
setEnabled(enabled) {
this.enabled = enabled;
}
handleClick() {
if (this.enabled) {
console.log(`点击了 ${this.type} "${this.name}"`);
this.handleEvent({ type: 'click', target: this });
}
}
}
// 按钮组件
class Button extends BasicComponent {
constructor(name, text) {
super(name, 'Button');
this.text = text;
}
render() {
if (!this.visible) return '';
const state = this.enabled ? '' : ' [disabled]';
return `Button "${this.text}" at (${this.x}, ${this.y})${state}`;
}
}
// 文本框组件
class TextBox extends BasicComponent {
constructor(name, placeholder = '') {
super(name, 'TextBox');
this.placeholder = placeholder;
this.value = '';
}
render() {
if (!this.visible) return '';
const state = this.enabled ? '' : ' [disabled]';
return `TextBox "${this.placeholder}" at (${this.x}, ${this.y})${state}`;
}
setValue(value) {
if (this.enabled) {
this.value = value;
}
}
}
// 标签组件
class Label extends BasicComponent {
constructor(name, text) {
super(name, 'Label');
this.text = text;
}
render() {
if (!this.visible) return '';
return `Label "${this.text}" at (${this.x}, ${this.y})`;
}
}
// 容器组件(组合节点)
class Container extends GUIComponent {
constructor(name) {
super(name);
this.children = [];
this.visible = true;
}
render() {
if (!this.visible) return '';
let result = `Container "${this.name}" at (${this.x}, ${this.y}):\n`;
for (const child of this.children) {
const childRender = child.render();
if (childRender) {
result += ` ${childRender}\n`;
}
}
return result;
}
add(component) {
this.children.push(component);
component.parent = this;
}
remove(component) {
const index = this.children.indexOf(component);
if (index !== -1) {
this.children.splice(index, 1);
component.parent = null;
}
}
getChild(index) {
return this.children[index];
}
getChildren() {
return [...this.children];
}
setVisible(visible) {
this.visible = visible;
// 递归设置子组件的可见性
for (const child of this.children) {
if (child.setVisible) {
child.setVisible(visible);
}
}
}
handleEvent(event) {
// 容器可以处理事件
console.log(`容器 "${this.name}" 处理事件: ${event.type}`);
// 传递给父容器
super.handleEvent(event);
}
findComponent(name) {
if (this.name === name) {
return this;
}
for (const child of this.children) {
if (child.name === name) {
return child;
}
if (child instanceof Container) {
const found = child.findComponent(name);
if (found) {
return found;
}
}
}
return null;
}
}
// 窗口容器
class Window extends Container {
constructor(name, width, height) {
super(name);
this.width = width;
this.height = height;
}
render() {
if (!this.visible) return '';
let result = `Window "${this.name}" (${this.width}x${this.height}) at (${this.x}, ${this.y}):\n`;
for (const child of this.children) {
const childRender = child.render();
if (childRender) {
result += ` ${childRender}\n`;
}
}
return result;
}
}
// 面板容器
class Panel extends Container {
constructor(name) {
super(name);
}
render() {
if (!this.visible) return '';
let result = `Panel "${this.name}" at (${this.x}, ${this.y}):\n`;
for (const child of this.children) {
const childRender = child.render();
if (childRender) {
result += ` ${childRender}\n`;
}
}
return result;
}
}
// GUI应用程序
class GUIApplication {
constructor() {
this.windows = [];
}
addWindow(window) {
this.windows.push(window);
}
render() {
let result = '=== GUI 应用程序 ===\n';
for (const window of this.windows) {
result += window.render() + '\n';
}
return result;
}
findComponent(name) {
for (const window of this.windows) {
const found = window.findComponent(name);
if (found) {
return found;
}
}
return null;
}
simulateClick(componentName) {
const component = this.findComponent(componentName);
if (component && component.handleClick) {
component.handleClick();
} else {
console.log(`未找到组件: ${componentName}`);
}
}
}
// 使用示例
function guiExample() {
const app = new GUIApplication();
console.log('=== 创建GUI界面 ===');
// 创建主窗口
const mainWindow = new Window('主窗口', 800, 600);
mainWindow.setPosition(100, 100);
// 创建菜单栏
const menuBar = new Panel('菜单栏');
menuBar.setPosition(0, 0);
const fileMenu = new Button('文件菜单', '文件');
fileMenu.setPosition(0, 0);
const editMenu = new Button('编辑菜单', '编辑');
editMenu.setPosition(60, 0);
menuBar.add(fileMenu);
menuBar.add(editMenu);
// 创建工具栏
const toolbar = new Panel('工具栏');
toolbar.setPosition(0, 30);
const newButton = new Button('新建按钮', '新建');
newButton.setPosition(0, 0);
const openButton = new Button('打开按钮', '打开');
openButton.setPosition(60, 0);
const saveButton = new Button('保存按钮', '保存');
saveButton.setPosition(120, 0);
toolbar.add(newButton);
toolbar.add(openButton);
toolbar.add(saveButton);
// 创建主内容区域
const contentPanel = new Panel('内容面板');
contentPanel.setPosition(0, 60);
const titleLabel = new Label('标题标签', '欢迎使用我们的应用程序');
titleLabel.setPosition(10, 10);
const nameTextBox = new TextBox('姓名输入框', '请输入姓名');
nameTextBox.setPosition(10, 40);
const emailTextBox = new TextBox('邮箱输入框', '请输入邮箱');
emailTextBox.setPosition(10, 70);
const submitButton = new Button('提交按钮', '提交');
submitButton.setPosition(10, 100);
contentPanel.add(titleLabel);
contentPanel.add(nameTextBox);
contentPanel.add(emailTextBox);
contentPanel.add(submitButton);
// 组装界面
mainWindow.add(menuBar);
mainWindow.add(toolbar);
mainWindow.add(contentPanel);
app.addWindow(mainWindow);
console.log('\n=== 渲染界面 ===');
console.log(app.render());
console.log('\n=== 模拟用户交互 ===');
app.simulateClick('提交按钮');
app.simulateClick('文件菜单');
app.simulateClick('不存在的按钮');
console.log('\n=== 查找并操作组件 ===');
const foundComponent = app.findComponent('邮箱输入框');
if (foundComponent) {
console.log(`找到组件: ${foundComponent.render()}`);
if (foundComponent.setValue) {
foundComponent.setValue('user@example.com');
console.log('已设置邮箱值');
}
}
console.log('\n=== 隐藏面板 ===');
contentPanel.setVisible(false);
console.log('隐藏内容面板后:');
console.log(app.render());
}
guiExample();3. 组织架构管理
在企业管理系统中,组合模式非常适合用于表示组织架构:
javascript
// 组织成员接口
class OrganizationMember {
constructor(name, position) {
this.name = name;
this.position = position;
}
getDetails() {
throw new Error('必须实现 getDetails 方法');
}
getSalary() {
throw new Error('必须实现 getSalary 方法');
}
getTotalSalary() {
throw new Error('必须实现 getTotalSalary 方法');
}
add(member) {
throw new Error('不支持添加操作');
}
remove(member) {
throw new Error('不支持删除操作');
}
getSubordinates() {
return [];
}
}
// 员工(叶子节点)
class Employee extends OrganizationMember {
constructor(name, position, salary) {
super(name, position);
this.salary = salary;
}
getDetails() {
return `${this.position}: ${this.name} (¥${this.salary})`;
}
getSalary() {
return this.salary;
}
getTotalSalary() {
return this.salary;
}
}
// 管理者(组合节点)
class Manager extends OrganizationMember {
constructor(name, position, salary) {
super(name, position);
this.salary = salary;
this.subordinates = [];
}
getDetails() {
return `${this.position}: ${this.name} (¥${this.salary}) 管理 ${this.subordinates.length} 名下属`;
}
getSalary() {
return this.salary;
}
getTotalSalary() {
let total = this.salary;
for (const subordinate of this.subordinates) {
total += subordinate.getTotalSalary();
}
return total;
}
add(member) {
this.subordinates.push(member);
}
remove(member) {
const index = this.subordinates.indexOf(member);
if (index !== -1) {
this.subordinates.splice(index, 1);
}
}
getSubordinates() {
return [...this.subordinates];
}
getOrganizationStructure(level = 0) {
const indent = ' '.repeat(level);
let result = `${indent}${this.getDetails()}\n`;
for (const subordinate of this.subordinates) {
if (subordinate instanceof Manager) {
result += subordinate.getOrganizationStructure(level + 1);
} else {
result += `${indent} ${subordinate.getDetails()}\n`;
}
}
return result;
}
findMember(name) {
if (this.name === name) {
return this;
}
for (const subordinate of this.subordinates) {
if (subordinate.name === name) {
return subordinate;
}
if (subordinate instanceof Manager) {
const found = subordinate.findMember(name);
if (found) {
return found;
}
}
}
return null;
}
getTeamMembers() {
const members = [this];
for (const subordinate of this.subordinates) {
if (subordinate instanceof Manager) {
members.push(...subordinate.getTeamMembers());
} else {
members.push(subordinate);
}
}
return members;
}
}
// 组织架构管理器
class OrganizationManager {
constructor(companyName) {
this.companyName = companyName;
this.ceo = null;
}
setCEO(ceo) {
this.ceo = ceo;
}
getOrganizationStructure() {
if (!this.ceo) {
return '尚未设置CEO';
}
return `公司: ${this.companyName}\n` + this.ceo.getOrganizationStructure();
}
getTotalSalary() {
if (!this.ceo) {
return 0;
}
return this.ceo.getTotalSalary();
}
findMember(name) {
if (!this.ceo) {
return null;
}
return this.ceo.findMember(name);
}
getTeamMembers(managerName) {
const manager = this.findMember(managerName);
if (manager instanceof Manager) {
return manager.getTeamMembers();
}
return [];
}
getManagers() {
const managers = [];
if (this.ceo instanceof Manager) {
const traverse = (member) => {
if (member instanceof Manager) {
managers.push(member);
for (const subordinate of member.getSubordinates()) {
traverse(subordinate);
}
}
};
traverse(this.ceo);
}
return managers;
}
}
// 使用示例
function organizationExample() {
const org = new OrganizationManager('科技有限公司');
console.log('=== 创建组织架构 ===');
// 创建CEO
const ceo = new Manager('张三', 'CEO', 100000);
// 创建部门经理
const techManager = new Manager('李四', '技术部经理', 80000);
const salesManager = new Manager('王五', '销售部经理', 75000);
const hrManager = new Manager('赵六', '人事部经理', 70000);
// 创建技术部员工
const developer1 = new Employee('小明', '高级开发工程师', 60000);
const developer2 = new Employee('小红', '中级开发工程师', 50000);
const tester = new Employee('小刚', '测试工程师', 45000);
// 创建销售部员工
const sales1 = new Employee('小李', '高级销售', 55000);
const sales2 = new Employee('小王', '中级销售', 45000);
// 创建人事部员工
const hrSpecialist = new Employee('小芳', '人事专员', 40000);
// 构建组织架构
techManager.add(developer1);
techManager.add(developer2);
techManager.add(tester);
salesManager.add(sales1);
salesManager.add(sales2);
hrManager.add(hrSpecialist);
ceo.add(techManager);
ceo.add(salesManager);
ceo.add(hrManager);
org.setCEO(ceo);
console.log('\n=== 显示组织架构 ===');
console.log(org.getOrganizationStructure());
console.log('\n=== 薪资统计 ===');
console.log(`公司总薪资支出: ¥${org.getTotalSalary()}`);
console.log('\n=== 查找员工 ===');
const foundMember = org.findMember('小红');
if (foundMember) {
console.log(`找到员工: ${foundMember.getDetails()}`);
}
console.log('\n=== 团队成员 ===');
const techTeam = org.getTeamMembers('李四');
console.log('技术部团队成员:');
techTeam.forEach(member => console.log(` ${member.getDetails()}`));
console.log('\n=== 管理层列表 ===');
const managers = org.getManagers();
console.log('所有管理者:');
managers.forEach(manager => console.log(` ${manager.getDetails()}`));
}
organizationExample();组合模式的两种实现方式
1. 透明方式
在透明方式中,Component接口声明了所有管理子对象的方法:
javascript
// 透明方式 - 所有方法都在接口中声明
class Component {
add(component) {
// 默认实现:不支持添加
throw new Error('不支持添加操作');
}
remove(component) {
// 默认实现:不支持删除
throw new Error('不支持删除操作');
}
getChild(index) {
// 默认实现:不支持获取子节点
throw new Error('不支持获取子节点操作');
}
}2. 安全方式
在安全方式中,只在Composite类中声明管理子对象的方法:
javascript
// 安全方式 - 只在组合节点中声明管理方法
class Component {
// 不声明管理子对象的方法
}
class Composite extends Component {
add(component) {
// 只在组合节点中实现
}
remove(component) {
// 只在组合节点中实现
}
getChild(index) {
// 只在组合节点中实现
}
}组合模式与其它模式的对比
组合模式 vs 装饰器模式
javascript
// 组合模式 - 表示"部分-整体"层次结构
class Composite {
add(component) {
this.children.push(component);
}
}
// 装饰器模式 - 动态添加职责
class Decorator {
constructor(component) {
this.component = component;
}
operation() {
// 增强原有功能
return this.component.operation();
}
}组合模式 vs 迭代器模式
javascript
// 组合模式 - 组织对象的层次结构
class Composite {
getChildren() {
return this.children;
}
}
// 迭代器模式 - 提供遍历元素的方法
class Iterator {
hasNext() {
// 判断是否还有下一个元素
}
next() {
// 获取下一个元素
}
}组合模式的优缺点
优点
- 统一接口:客户端可以一致地使用单个对象和组合对象
- 简化客户端:无需区分叶子节点和组合节点
- 增加灵活性:可以很容易地增加新的组件类型
- 递归遍历:天然支持递归操作和遍历
- 符合开闭原则:添加新组件无需修改现有代码
缺点
- 设计复杂:需要正确识别层次结构
- 类型安全:难以限制容器中的构件类型
- 性能开销:递归操作可能带来性能问题
- 过度泛化:可能被过度使用
总结
组合模式是一种结构型设计模式,它允许你将对象组合成树形结构来表示"部分-整体"的层次关系。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
通过本章的学习,我们了解了:
- 组合模式的基本概念和核心思想
- 组合模式的两种实现方式(透明方式和安全方式)
- 组合模式在实际开发中的应用场景(文件系统、GUI界面、组织架构)
- 组合模式与其他结构型模式的对比
- 组合模式的优缺点
组合模式在现代软件开发中应用广泛,特别是在需要表示对象层次结构、统一处理单个对象和对象组合的场景中,它可以很好地支持系统的灵活性和可维护性。
在下一章中,我们将继续探讨其他结构型模式,接下来是享元模式。