深入理解设计模式之组合模式(Composite Pattern)
一、引言
在软件开发中,我们经常需要处理树形结构的数据。比如:文件系统中的文件和文件夹、公司的组织架构、菜单系统、XML文档结构等。这些场景有一个共同的特点:它们都是树形结构,并且叶子节点和容器节点在某些操作上需要保持一致性。
传统的处理方式是为叶子节点和容器节点定义不同的类,客户端需要区分它们并使用不同的方式处理。这会导致客户端代码复杂,耦合度高,难以维护。
ini
传统方式的问题:
if (node instanceof File) {
File file = (File) node;
file.display();
} else if (node instanceof Folder) {
Folder folder = (Folder) node;
folder.display();
for (Component child : folder.getChildren()) {
// 递归处理...需要再次判断类型
}
}
代码充满了类型判断,难以扩展
组合模式为这类问题提供了优雅的解决方案。它就像现实中的文件系统:无论是文件还是文件夹,你都可以用统一的方式打开、删除、移动它们;文件夹可以包含文件,也可以包含其他文件夹,形成树形结构。
本文将深入探讨组合模式的原理、实现方式,并结合文件系统、组织架构、Swing组件树等实际应用,帮助你全面掌握这一重要的设计模式。
二、什么是组合模式
2.1 定义
组合模式(Composite Pattern)是一种结构型设计模式,它将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
2.2 核心思想
- 统一对待:叶子节点和容器节点实现相同的接口,客户端统一对待
- 树形结构:将对象组合成树形结构表示层次关系
- 递归组合:容器对象可以包含叶子对象,也可以包含其他容器对象
- 透明性:客户端无需区分叶子节点和容器节点
2.3 模式结构
scss
组合模式结构:
┌───────────────────────┐
│ <<interface>> │
│ Component │ 抽象组件
├───────────────────────┤
│ + operation() │ 通用操作
│ + add(Component) │ 添加子节点
│ + remove(Component) │ 移除子节点
│ + getChild(int) │ 获取子节点
└──────────┬────────────┘
△
│ 实现
┌──────┴──────┐
│ │
┌───┴──────┐ ┌──┴─────────────┐
│ Leaf │ │ Composite │ 容器节点
├──────────┤ ├────────────────┤
│operation │ │- children: List│ 包含子节点
└──────────┘ │+ operation() │
│+ add() │
│+ remove() │
│+ getChild() │
└────────────────┘
树形结构示例:
Composite(root)
/ | \
/ | \
Leaf1 Composite2 Leaf2
/ \
/ \
Leaf3 Leaf4
客户端代码:
Component component = getComponent(); // 可能是Leaf或Composite
component.operation(); // 统一调用,无需判断类型
2.4 组合模式的两种实现
scss
1. 透明方式(Transparent):
- 在Component中声明所有方法(包括管理子节点的方法)
- 优点:客户端完全透明,无需区分类型
- 缺点:Leaf需要实现管理方法(但实际不支持)
Component
├─ operation()
├─ add() ← Leaf也有这些方法(但会抛异常)
├─ remove()
└─ getChild()
2. 安全方式(Safe):
- 在Component中只声明共同方法
- 管理方法只在Composite中定义
- 优点:类型安全,Leaf不会有无效方法
- 缺点:客户端需要区分Leaf和Composite
Component
└─ operation() ← 只有通用方法
Composite extends Component
├─ add() ← 管理方法只在这里
├─ remove()
└─ getChild()
三、基础示例
3.1 场景:文件系统
文件系统是组合模式最经典的应用场景。
抽象组件(透明方式):
java
import java.util.ArrayList;
import java.util.List;
/**
* 抽象组件:文件系统组件
*/
public abstract class FileSystemComponent {
protected String name;
public FileSystemComponent(String name) {
this.name = name;
}
/**
* 显示组件信息(通用方法)
*/
public abstract void display(int depth);
/**
* 添加子组件(容器方法)
* 叶子节点不支持,抛出异常
*/
public void add(FileSystemComponent component) {
throw new UnsupportedOperationException("不支持此操作");
}
/**
* 移除子组件(容器方法)
*/
public void remove(FileSystemComponent component) {
throw new UnsupportedOperationException("不支持此操作");
}
/**
* 获取子组件(容器方法)
*/
public FileSystemComponent getChild(int index) {
throw new UnsupportedOperationException("不支持此操作");
}
/**
* 获取大小
*/
public abstract long getSize();
public String getName() {
return name;
}
}
叶子节点:
java
/**
* 叶子节点:文件
*/
public class File extends FileSystemComponent {
private long size;
public File(String name, long size) {
super(name);
this.size = size;
}
@Override
public void display(int depth) {
// 缩进显示
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
System.out.println(indent + "📄 文件: " + name + " (" + size + " KB)");
}
@Override
public long getSize() {
return size;
}
}
容器节点:
java
/**
* 容器节点:文件夹
*/
public class Folder extends FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
public Folder(String name) {
super(name);
}
@Override
public void add(FileSystemComponent component) {
children.add(component);
}
@Override
public void remove(FileSystemComponent component) {
children.remove(component);
}
@Override
public FileSystemComponent getChild(int index) {
return children.get(index);
}
@Override
public void display(int depth) {
// 显示文件夹
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
System.out.println(indent + "📁 文件夹: " + name);
// 递归显示所有子组件
for (FileSystemComponent child : children) {
child.display(depth + 1);
}
}
@Override
public long getSize() {
// 文件夹大小 = 所有子组件大小之和
long total = 0;
for (FileSystemComponent child : children) {
total += child.getSize();
}
return total;
}
public List<FileSystemComponent> getChildren() {
return children;
}
}
客户端使用:
java
public class FileSystemDemo {
public static void main(String[] args) {
// 创建根文件夹
Folder root = new Folder("根目录");
// 创建documents文件夹
Folder documents = new Folder("Documents");
documents.add(new File("简历.docx", 50));
documents.add(new File("项目报告.pdf", 120));
// 创建photos文件夹
Folder photos = new Folder("Photos");
photos.add(new File("照片1.jpg", 300));
photos.add(new File("照片2.jpg", 280));
// 创建photos的子文件夹
Folder vacation = new Folder("Vacation");
vacation.add(new File("海滩.jpg", 400));
vacation.add(new File("山景.jpg", 350));
photos.add(vacation);
// 创建music文件夹
Folder music = new Folder("Music");
music.add(new File("歌曲1.mp3", 5000));
music.add(new File("歌曲2.mp3", 4800));
// 组装树形结构
root.add(documents);
root.add(photos);
root.add(music);
root.add(new File("README.txt", 5));
// 显示整个文件系统(统一操作)
System.out.println("========== 文件系统结构 ==========\n");
root.display(0);
// 计算总大小(统一操作)
System.out.println("\n========== 大小统计 ==========");
System.out.println("根目录总大小: " + root.getSize() + " KB");
System.out.println("Documents大小: " + documents.getSize() + " KB");
System.out.println("Photos大小: " + photos.getSize() + " KB");
// 演示透明性:客户端无需区分File和Folder
System.out.println("\n========== 演示透明性 ==========");
processComponent(root);
processComponent(documents);
processComponent(new File("test.txt", 10));
}
/**
* 统一处理组件(无需判断类型)
*/
private static void processComponent(FileSystemComponent component) {
System.out.println("处理组件: " + component.getName() +
", 大小: " + component.getSize() + " KB");
}
}
输出:
makefile
========== 文件系统结构 ==========
📁 文件夹: 根目录
📁 文件夹: Documents
📄 文件: 简历.docx (50 KB)
📄 文件: 项目报告.pdf (120 KB)
📁 文件夹: Photos
📄 文件: 照片1.jpg (300 KB)
📄 文件: 照片2.jpg (280 KB)
📁 文件夹: Vacation
📄 文件: 海滩.jpg (400 KB)
📄 文件: 山景.jpg (350 KB)
📁 文件夹: Music
📄 文件: 歌曲1.mp3 (5000 KB)
📄 文件: 歌曲2.mp3 (4800 KB)
📄 文件: README.txt (5 KB)
========== 大小统计 ==========
根目录总大小: 11305 KB
Documents大小: 170 KB
Photos大小: 1330 KB
========== 演示透明性 ==========
处理组件: 根目录, 大小: 11305 KB
处理组件: Documents, 大小: 170 KB
处理组件: test.txt, 大小: 10 KB
四、实际生产场景应用
4.1 场景:企业组织架构
企业的组织架构天然就是树形结构。
java
/**
* 抽象组件:组织单元
*/
abstract class OrganizationComponent {
protected String name;
protected String position;
public OrganizationComponent(String name, String position) {
this.name = name;
this.position = position;
}
/**
* 显示组织结构
*/
public abstract void display(int depth);
/**
* 添加下属
*/
public void add(OrganizationComponent component) {
throw new UnsupportedOperationException();
}
/**
* 移除下属
*/
public void remove(OrganizationComponent component) {
throw new UnsupportedOperationException();
}
/**
* 计算总人数
*/
public abstract int getEmployeeCount();
/**
* 计算总工资
*/
public abstract double getTotalSalary();
public String getName() { return name; }
public String getPosition() { return position; }
}
/**
* 叶子节点:员工
*/
class Employee extends OrganizationComponent {
private double salary;
public Employee(String name, String position, double salary) {
super(name, position);
this.salary = salary;
}
@Override
public void display(int depth) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
System.out.println(indent + "👤 " + position + ": " + name +
" (工资: ¥" + salary + ")");
}
@Override
public int getEmployeeCount() {
return 1;
}
@Override
public double getTotalSalary() {
return salary;
}
public double getSalary() {
return salary;
}
}
/**
* 容器节点:部门
*/
class Department extends OrganizationComponent {
private List<OrganizationComponent> members = new ArrayList<>();
public Department(String name) {
super(name, "部门");
}
@Override
public void add(OrganizationComponent component) {
members.add(component);
}
@Override
public void remove(OrganizationComponent component) {
members.remove(component);
}
@Override
public void display(int depth) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < depth; i++) {
indent.append(" ");
}
System.out.println(indent + "🏢 " + name);
// 递归显示所有成员
for (OrganizationComponent member : members) {
member.display(depth + 1);
}
}
@Override
public int getEmployeeCount() {
int count = 0;
for (OrganizationComponent member : members) {
count += member.getEmployeeCount();
}
return count;
}
@Override
public double getTotalSalary() {
double total = 0;
for (OrganizationComponent member : members) {
total += member.getTotalSalary();
}
return total;
}
}
/**
* 测试组织架构
*/
class OrganizationDemo {
public static void main(String[] args) {
// 创建公司
Department company = new Department("科技有限公司");
// 创建技术部
Department techDept = new Department("技术部");
techDept.add(new Employee("张三", "技术总监", 30000));
techDept.add(new Employee("李四", "高级工程师", 20000));
techDept.add(new Employee("王五", "工程师", 15000));
// 创建销售部
Department salesDept = new Department("销售部");
salesDept.add(new Employee("赵六", "销售总监", 25000));
salesDept.add(new Employee("钱七", "销售经理", 18000));
// 创建销售部的子部门
Department regionalSales = new Department("华东区销售");
regionalSales.add(new Employee("孙八", "区域经理", 16000));
regionalSales.add(new Employee("周九", "销售代表", 12000));
salesDept.add(regionalSales);
// 创建人事部
Department hrDept = new Department("人事部");
hrDept.add(new Employee("吴十", "人事经理", 18000));
hrDept.add(new Employee("郑十一", "招聘专员", 12000));
// 组装公司结构
company.add(new Employee("CEO", "首席执行官", 50000));
company.add(techDept);
company.add(salesDept);
company.add(hrDept);
// 显示组织结构
System.out.println("========== 组织架构 ==========\n");
company.display(0);
// 统计信息
System.out.println("\n========== 统计信息 ==========");
System.out.println("总人数: " + company.getEmployeeCount());
System.out.println("总工资: ¥" + company.getTotalSalary());
System.out.println("技术部人数: " + techDept.getEmployeeCount());
System.out.println("技术部工资: ¥" + techDept.getTotalSalary());
}
}
4.2 场景:菜单系统
Web应用中的多级菜单系统。
java
/**
* 抽象组件:菜单组件
*/
abstract class MenuComponent {
protected String name;
protected String url;
protected String icon;
public MenuComponent(String name, String url, String icon) {
this.name = name;
this.url = url;
this.icon = icon;
}
/**
* 渲染菜单
*/
public abstract void render(int level);
/**
* 添加子菜单
*/
public void add(MenuComponent component) {
throw new UnsupportedOperationException();
}
/**
* 移除子菜单
*/
public void remove(MenuComponent component) {
throw new UnsupportedOperationException();
}
/**
* 检查权限
*/
public abstract boolean hasPermission(String userId);
public String getName() { return name; }
public String getUrl() { return url; }
}
/**
* 叶子节点:菜单项
*/
class MenuItem extends MenuComponent {
private String permission; // 权限标识
public MenuItem(String name, String url, String icon, String permission) {
super(name, url, icon);
this.permission = permission;
}
@Override
public void render(int level) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < level; i++) {
indent.append(" ");
}
System.out.println(indent + icon + " " + name + " -> " + url);
}
@Override
public boolean hasPermission(String userId) {
// 简化:实际应该查询数据库
return true;
}
}
/**
* 容器节点:菜单组
*/
class Menu extends MenuComponent {
private List<MenuComponent> children = new ArrayList<>();
public Menu(String name, String icon) {
super(name, null, icon);
}
@Override
public void add(MenuComponent component) {
children.add(component);
}
@Override
public void remove(MenuComponent component) {
children.remove(component);
}
@Override
public void render(int level) {
StringBuilder indent = new StringBuilder();
for (int i = 0; i < level; i++) {
indent.append(" ");
}
System.out.println(indent + icon + " " + name);
// 递归渲染子菜单
for (MenuComponent child : children) {
child.render(level + 1);
}
}
@Override
public boolean hasPermission(String userId) {
// 菜单组:只要有一个子菜单有权限就显示
for (MenuComponent child : children) {
if (child.hasPermission(userId)) {
return true;
}
}
return false;
}
/**
* 获取有权限的子菜单
*/
public List<MenuComponent> getAuthorizedChildren(String userId) {
List<MenuComponent> result = new ArrayList<>();
for (MenuComponent child : children) {
if (child.hasPermission(userId)) {
result.add(child);
}
}
return result;
}
}
/**
* 测试菜单系统
*/
class MenuSystemDemo {
public static void main(String[] args) {
// 创建菜单结构
Menu mainMenu = new Menu("主菜单", "🏠");
// 系统管理菜单
Menu systemMenu = new Menu("系统管理", "⚙️");
systemMenu.add(new MenuItem("用户管理", "/system/users", "👥", "system:user"));
systemMenu.add(new MenuItem("角色管理", "/system/roles", "🎭", "system:role"));
systemMenu.add(new MenuItem("权限管理", "/system/permissions", "🔐", "system:permission"));
// 业务管理菜单
Menu businessMenu = new Menu("业务管理", "💼");
Menu orderMenu = new Menu("订单管理", "📋");
orderMenu.add(new MenuItem("订单列表", "/business/orders", "📝", "business:order:list"));
orderMenu.add(new MenuItem("订单统计", "/business/orders/stats", "📊", "business:order:stats"));
Menu productMenu = new Menu("商品管理", "📦");
productMenu.add(new MenuItem("商品列表", "/business/products", "📄", "business:product:list"));
productMenu.add(new MenuItem("分类管理", "/business/categories", "🏷️", "business:category"));
businessMenu.add(orderMenu);
businessMenu.add(productMenu);
// 报表中心
Menu reportMenu = new Menu("报表中心", "📈");
reportMenu.add(new MenuItem("销售报表", "/reports/sales", "💰", "report:sales"));
reportMenu.add(new MenuItem("库存报表", "/reports/inventory", "📦", "report:inventory"));
// 组装主菜单
mainMenu.add(systemMenu);
mainMenu.add(businessMenu);
mainMenu.add(reportMenu);
mainMenu.add(new MenuItem("个人中心", "/profile", "👤", "profile"));
// 渲染完整菜单
System.out.println("========== 完整菜单结构 ==========\n");
mainMenu.render(0);
// 模拟根据权限渲染菜单
System.out.println("\n========== 根据权限过滤后的菜单 ==========\n");
renderAuthorizedMenu(mainMenu, "user123", 0);
}
/**
* 渲染有权限的菜单
*/
private static void renderAuthorizedMenu(MenuComponent menu, String userId, int level) {
if (menu.hasPermission(userId)) {
menu.render(level);
}
}
}
五、开源框架中的应用
5.1 AWT/Swing组件树
Java GUI框架使用组合模式构建组件树。
java
/**
* Swing组件树示例
*
* Component (抽象组件)
* ├─ Container (容器组件)
* │ ├─ JPanel
* │ ├─ JFrame
* │ └─ JDialog
* └─ JComponent (叶子组件)
* ├─ JButton
* ├─ JLabel
* └─ JTextField
*/
import javax.swing.*;
import java.awt.*;
class SwingCompositeDemo {
public static void main(String[] args) {
// 创建窗口(容器)
JFrame frame = new JFrame("组合模式示例");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
// 创建面板(容器)
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
// 添加组件(叶子)
panel.add(new JLabel("用户名:"));
panel.add(new JTextField(15));
panel.add(new JLabel("密码:"));
panel.add(new JPasswordField(15));
panel.add(new JButton("登录"));
// 容器包含容器
frame.add(panel);
frame.setVisible(true);
// 组合模式的体现:
// 1. Container和Component都是组件
// 2. Container可以包含Component
// 3. Container也可以包含其他Container
// 4. 统一的操作:setVisible(), setEnabled()等
}
}
/**
* 简化的Swing组件结构
*/
abstract class Component {
protected String name;
public abstract void paint();
public abstract void setVisible(boolean visible);
// 容器方法(透明方式)
public void add(Component component) {
throw new UnsupportedOperationException();
}
public void remove(Component component) {
throw new UnsupportedOperationException();
}
}
class Container extends Component {
private List<Component> children = new ArrayList<>();
@Override
public void paint() {
System.out.println("绘制容器: " + name);
for (Component child : children) {
child.paint();
}
}
@Override
public void setVisible(boolean visible) {
System.out.println((visible ? "显示" : "隐藏") + "容器: " + name);
// 递归设置所有子组件
for (Component child : children) {
child.setVisible(visible);
}
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
}
class Button extends Component {
@Override
public void paint() {
System.out.println("绘制按钮: " + name);
}
@Override
public void setVisible(boolean visible) {
System.out.println((visible ? "显示" : "隐藏") + "按钮: " + name);
}
}
5.2 MyBatis动态SQL
MyBatis的SqlNode使用组合模式构建动态SQL。
java
/**
* MyBatis的SqlNode接口(简化版)
*
* 用于构建动态SQL的树形结构
*/
interface SqlNode {
/**
* 应用节点,生成SQL片段
*/
boolean apply(DynamicContext context);
}
/**
* 动态上下文
*/
class DynamicContext {
private StringBuilder sqlBuilder = new StringBuilder();
private Map<String, Object> bindings = new HashMap<>();
public void appendSql(String sql) {
sqlBuilder.append(sql).append(" ");
}
public String getSql() {
return sqlBuilder.toString().trim();
}
public Map<String, Object> getBindings() {
return bindings;
}
}
/**
* 叶子节点:静态文本节点
*/
class StaticTextSqlNode implements SqlNode {
private String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
/**
* 容器节点:混合SQL节点
*/
class MixedSqlNode implements SqlNode {
private List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
// 递归应用所有子节点
for (SqlNode node : contents) {
node.apply(context);
}
return true;
}
}
/**
* 条件节点:IF节点
*/
class IfSqlNode implements SqlNode {
private String test;
private SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
// 简化:实际会解析test表达式
if (evaluateTest(test, context)) {
contents.apply(context);
return true;
}
return false;
}
private boolean evaluateTest(String test, DynamicContext context) {
// 简化实现
return true;
}
}
/**
* 测试MyBatis SqlNode
*/
class MyBatisSqlNodeDemo {
public static void main(String[] args) {
// 构建SQL树:SELECT * FROM users WHERE 1=1 <if test="name != null">AND name = #{name}</if>
List<SqlNode> nodes = new ArrayList<>();
nodes.add(new StaticTextSqlNode("SELECT * FROM users WHERE 1=1"));
// IF节点
SqlNode ifContents = new StaticTextSqlNode("AND name = #{name}");
nodes.add(new IfSqlNode(ifContents, "name != null"));
// 创建根节点
MixedSqlNode rootNode = new MixedSqlNode(nodes);
// 应用节点,生成SQL
DynamicContext context = new DynamicContext();
rootNode.apply(context);
System.out.println("生成的SQL: " + context.getSql());
System.out.println("\n组合模式的体现:");
System.out.println("1. SqlNode是抽象组件");
System.out.println("2. StaticTextSqlNode是叶子节点");
System.out.println("3. MixedSqlNode、IfSqlNode是容器节点");
System.out.println("4. 可以组合成复杂的动态SQL树");
}
}
六、组合模式的优缺点
6.1 优点
1. 统一对待单个对象和组合对象
java
// 无需类型判断
void process(Component component) {
component.operation(); // 统一调用
}
// 而不是:
void process(Object obj) {
if (obj instanceof Leaf) {
((Leaf) obj).operation();
} else if (obj instanceof Composite) {
((Composite) obj).operation();
}
}
2. 简化客户端代码
diff
客户端不需要关心:
- 是叶子节点还是容器节点
- 树的层次结构
- 递归遍历逻辑
只需统一调用接口方法即可
3. 易于扩展
添加新的组件类型:
只需实现Component接口
不影响现有代码
4. 符合开闭原则
对扩展开放:可以添加新的Leaf或Composite
对修改封闭:无需修改现有代码
6.2 缺点
1. 设计较为抽象
需要合理设计组件接口
需要理解递归结构
新手可能难以理解
2. 限制组件类型困难
Composite可以包含任何Component
如果想限制只能包含特定类型,实现起来困难
3. 透明方式的问题
csharp
Leaf需要实现管理方法(如add、remove)
但实际不支持,只能抛异常
违反了类型安全
七、最佳实践
7.1 选择合适的实现方式
java
/**
* 透明方式 vs 安全方式
*/
// 1. 透明方式:适用于希望客户端完全透明的场景
abstract class TransparentComponent {
public abstract void operation();
public void add(TransparentComponent c) {
throw new UnsupportedOperationException();
}
public void remove(TransparentComponent c) {
throw new UnsupportedOperationException();
}
}
// 2. 安全方式:适用于类型安全要求高的场景
interface SafeComponent {
void operation();
}
class SafeComposite implements SafeComponent {
private List<SafeComponent> children = new ArrayList<>();
@Override
public void operation() {
for (SafeComponent child : children) {
child.operation();
}
}
// 只在Composite中定义管理方法
public void add(SafeComponent component) {
children.add(component);
}
public void remove(SafeComponent component) {
children.remove(component);
}
}
7.2 提供遍历器
java
/**
* 提供迭代器遍历组合结构
*/
class CompositeWithIterator extends FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
public CompositeWithIterator(String name) {
super(name);
}
/**
* 提供迭代器
*/
public Iterator<FileSystemComponent> iterator() {
return children.iterator();
}
/**
* 深度优先遍历
*/
public Iterator<FileSystemComponent> deepIterator() {
return new CompositeIterator(children.iterator());
}
// ... 其他方法
}
/**
* 组合迭代器(深度优先)
*/
class CompositeIterator implements Iterator<FileSystemComponent> {
private Stack<Iterator<FileSystemComponent>> stack = new Stack<>();
public CompositeIterator(Iterator<FileSystemComponent> iterator) {
stack.push(iterator);
}
@Override
public boolean hasNext() {
if (stack.isEmpty()) {
return false;
}
Iterator<FileSystemComponent> iterator = stack.peek();
if (!iterator.hasNext()) {
stack.pop();
return hasNext();
}
return true;
}
@Override
public FileSystemComponent next() {
if (hasNext()) {
Iterator<FileSystemComponent> iterator = stack.peek();
FileSystemComponent component = iterator.next();
// 如果是容器,将其子元素压入栈
if (component instanceof CompositeWithIterator) {
stack.push(((CompositeWithIterator) component).iterator());
}
return component;
}
return null;
}
}
7.3 缓存计算结果
java
/**
* 缓存昂贵的计算结果
*/
class CachedComposite extends FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
private Long cachedSize = null; // 缓存大小
public CachedComposite(String name) {
super(name);
}
@Override
public void add(FileSystemComponent component) {
children.add(component);
// 添加子组件时清除缓存
cachedSize = null;
}
@Override
public void remove(FileSystemComponent component) {
children.remove(component);
// 移除子组件时清除缓存
cachedSize = null;
}
@Override
public long getSize() {
if (cachedSize == null) {
// 计算并缓存
long total = 0;
for (FileSystemComponent child : children) {
total += child.getSize();
}
cachedSize = total;
}
return cachedSize;
}
@Override
public void display(int depth) {
// 实现显示逻辑
}
}
7.4 使用访问者模式增强
java
/**
* 结合访问者模式处理不同操作
*/
interface ComponentVisitor {
void visitFile(File file);
void visitFolder(Folder folder);
}
abstract class VisitableComponent extends FileSystemComponent {
public VisitableComponent(String name) {
super(name);
}
public abstract void accept(ComponentVisitor visitor);
}
/**
* 计算文件数量的访问者
*/
class FileCountVisitor implements ComponentVisitor {
private int fileCount = 0;
@Override
public void visitFile(File file) {
fileCount++;
}
@Override
public void visitFolder(Folder folder) {
// 文件夹本身不计数
}
public int getFileCount() {
return fileCount;
}
}
八、总结
8.1 核心要点
- 组合模式的本质:将对象组合成树形结构,统一对待单个对象和组合对象
- 关键角色 :
- Component:抽象组件,定义统一接口
- Leaf:叶子节点,不能包含子节点
- Composite:容器节点,可以包含子节点
- 适用场景 :
- 需要表示对象的部分-整体层次结构
- 希望统一对待单个对象和组合对象
- 树形结构的数据
- 核心优势 :
- 简化客户端代码
- 易于扩展
- 符合开闭原则
8.2 使用建议
arduino
选择组合模式的检查清单:
✓ 数据结构是否是树形的?
✓ 是否需要统一对待叶子和容器?
✓ 是否需要递归处理?
✓ 是否希望客户端代码简单?
如果有3个以上"是",建议使用组合模式!
8.3 与其他模式的对比
diff
组合 vs 装饰者:
- 组合:关注部分-整体关系,一对多
- 装饰者:关注动态添加职责,一对一
组合 vs 迭代器:
- 组合:定义树形结构
- 迭代器:遍历组合结构
组合 vs 访问者:
- 组合:定义结构
- 访问者:对结构执行操作
8.4 实践经验
- 合理选择实现方式:透明方式 vs 安全方式
- 提供便利方法:如迭代器、深度优先遍历等
- 考虑性能优化:缓存计算结果,避免重复计算
- 注意内存管理:深层嵌套可能导致栈溢出
- 结合其他模式:可以与访问者、迭代器等模式结合