一、模式哲学与设计原理
1.1 模式本质
组合模式(Composite Pattern)是一种结构型设计模式 ,允许你将对象组合成树形结构 来表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
1.2 数学表达
组合模式体现了递归结构:
- 叶子节点:基节点,无子节点
- 组合节点:可包含子节点(叶子节点或其他组合节点)
- 树形结构:层次化的递归结构
1.3 设计原则体现
- 开闭原则:新增组件类型无需修改现有代码
- 单一职责原则:组件接口定义通用操作
- 里氏替换原则:叶子节点和组合节点可相互替换
- 接口隔离原则:合理设计组件接口
二、Java源代码实现
2.1 基本实现
组件接口
csharp
// 抽象组件
public abstract class Component {
protected String name;
public Component(String name) {
this.name = name;
}
public abstract void add(Component component);
public abstract void remove(Component component);
public abstract void display(int depth);
public abstract int getSize();
public String getName() {
return name;
}
}
叶子节点
arduino
// 叶子节点
public class Leaf extends Component {
private int size;
public Leaf(String name, int size) {
super(name);
this.size = size;
}
@Override
public void add(Component component) {
throw new UnsupportedOperationException("叶子节点不能添加子节点");
}
@Override
public void remove(Component component) {
throw new UnsupportedOperationException("叶子节点不能移除子节点");
}
@Override
public void display(int depth) {
String indent = " ".repeat(depth);
System.out.println(indent + "- " + name + " (" + size + " bytes)");
}
@Override
public int getSize() {
return size;
}
}
组合节点
typescript
// 组合节点
public class Composite extends Component {
private List<Component> children = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void add(Component component) {
children.add(component);
}
@Override
public void remove(Component component) {
children.remove(component);
}
@Override
public void display(int depth) {
String indent = " ".repeat(depth);
System.out.println(indent + "+ " + name);
for (Component child : children) {
child.display(depth + 1);
}
}
@Override
public int getSize() {
int total = 0;
for (Component child : children) {
total += child.getSize();
}
return total;
}
public List<Component> getChildren() {
return new ArrayList<>(children);
}
}
2.2 客户端使用
csharp
public class Client {
public static void main(String[] args) {
// 创建文件系统结构
Composite root = new Composite("根目录");
// 创建子目录
Composite dir1 = new Composite("文档");
Composite dir2 = new Composite("图片");
// 创建文件
Leaf file1 = new Leaf("报告.docx", 1024);
Leaf file2 = new Leaf("简历.pdf", 2048);
Leaf file3 = new Leaf("照片1.jpg", 5120);
Leaf file4 = new Leaf("照片2.png", 3072);
// 构建树形结构
root.add(dir1);
root.add(dir2);
dir1.add(file1);
dir1.add(file2);
dir2.add(file3);
dir2.add(file4);
// 添加嵌套目录
Composite subDir = new Composite("2023");
Leaf file5 = new Leaf("年度报告.pdf", 4096);
subDir.add(file5);
dir1.add(subDir);
// 显示结构
System.out.println("文件系统结构:");
root.display(0);
// 计算总大小
System.out.println("\n总大小: " + root.getSize() + " bytes");
System.out.println("文档大小: " + dir1.getSize() + " bytes");
System.out.println("图片大小: " + dir2.getSize() + " bytes");
// 搜索功能
System.out.println("\n搜索包含'报告'的文件:");
search(root, "报告");
}
public static void search(Component component, String keyword) {
if (component.getName().contains(keyword)) {
System.out.println("找到: " + component.getName());
}
if (component instanceof Composite) {
Composite composite = (Composite) component;
for (Component child : composite.getChildren()) {
search(child, keyword);
}
}
}
}
三、应用场景深度解析
3.1 图形界面系统
csharp
// GUI组件示例
public abstract class GUIComponent {
public abstract void draw();
public abstract void add(GUIComponent component);
public abstract void remove(GUIComponent component);
public abstract GUIComponent getChild(int index);
}
// 窗口容器
public class Window extends GUIComponent {
private List<GUIComponent> children = new ArrayList<>();
@Override
public void draw() {
System.out.println("绘制窗口");
for (GUIComponent child : children) {
child.draw();
}
}
// 其他方法实现...
}
// 按钮组件
public class Button extends GUIComponent {
@Override
public void draw() {
System.out.println("绘制按钮");
}
@Override
public void add(GUIComponent component) {
throw new UnsupportedOperationException("按钮不能包含子组件");
}
// 其他方法...
}
3.2 组织结构管理
java
// 组织结构组件
public abstract class OrganizationComponent {
protected String name;
public OrganizationComponent(String name) {
this.name = name;
}
public abstract void add(OrganizationComponent component);
public abstract void remove(OrganizationComponent component);
public abstract void print();
public abstract int getEmployeeCount();
}
// 部门
public class Department extends OrganizationComponent {
private List<OrganizationComponent> children = new ArrayList<>();
public Department(String name) {
super(name);
}
@Override
public int getEmployeeCount() {
int count = 0;
for (OrganizationComponent child : children) {
count += child.getEmployeeCount();
}
return count;
}
// 其他方法...
}
// 员工
public class Employee extends OrganizationComponent {
public Employee(String name) {
super(name);
}
@Override
public int getEmployeeCount() {
return 1; // 每个员工计数为1
}
// 其他方法...
}
3.3 电子商务商品分类
java
// 商品分类组件
public abstract class ProductCategory {
protected String name;
public ProductCategory(String name) {
this.name = name;
}
public abstract void add(ProductCategory category);
public abstract void remove(ProductCategory category);
public abstract double getTotalPrice();
public abstract int getTotalCount();
}
// 商品
public class Product extends ProductCategory {
private double price;
public Product(String name, double price) {
super(name);
this.price = price;
}
@Override
public double getTotalPrice() {
return price;
}
@Override
public int getTotalCount() {
return 1;
}
// 其他方法...
}
// 商品分类
public class Category extends ProductCategory {
private List<ProductCategory> products = new ArrayList<>();
public Category(String name) {
super(name);
}
@Override
public double getTotalPrice() {
double total = 0;
for (ProductCategory product : products) {
total += product.getTotalPrice();
}
return total;
}
@Override
public int getTotalCount() {
int count = 0;
for (ProductCategory product : products) {
count += product.getTotalCount();
}
return count;
}
// 其他方法...
}
3.4 表达式计算
java
// 表达式组件
public interface Expression {
double evaluate();
void add(Expression expression);
void remove(Expression expression);
}
// 数字表达式
public class NumberExpression implements Expression {
private double value;
public NumberExpression(double value) {
this.value = value;
}
@Override
public double evaluate() {
return value;
}
@Override
public void add(Expression expression) {
throw new UnsupportedOperationException();
}
// 其他方法...
}
// 加法表达式
public class AddExpression implements Expression {
private List<Expression> expressions = new ArrayList<>();
@Override
public double evaluate() {
double result = 0;
for (Expression expr : expressions) {
result += expr.evaluate();
}
return result;
}
@Override
public void add(Expression expression) {
expressions.add(expression);
}
// 其他方法...
}
四、优缺点深度分析
4.1 优点详细分析
1. 统一处理
csharp
// 客户端可以统一处理所有组件
public void processComponent(Component component) {
// 不需要知道是叶子节点还是组合节点
System.out.println("名称: " + component.getName());
System.out.println("大小: " + component.getSize());
// 统一操作
component.display(0);
}
2. 简化客户端代码
scss
// 传统方式需要类型判断
public void process(Object obj) {
if (obj instanceof File) {
File file = (File) obj;
// 处理文件
} else if (obj instanceof Directory) {
Directory dir = (Directory) obj;
// 处理目录
for (File child : dir.getFiles()) {
process(child);
}
}
}
// 组合模式方式
public void process(Component component) {
component.display(0);
// 递归处理子组件
if (component instanceof Composite) {
Composite composite = (Composite) component;
for (Component child : composite.getChildren()) {
process(child);
}
}
}
3. 易于扩展
scala
// 新增组件类型很容易
public class Link extends Component {
private Component target;
public Link(String name, Component target) {
super(name);
this.target = target;
}
@Override
public int getSize() {
return target.getSize(); // 链接文件大小同目标文件
}
// 其他实现...
}
4. 支持复杂结构
ini
// 可以构建任意复杂的树形结构
Component root = new Composite("root");
// 创建多级嵌套结构
for (int i = 0; i < 3; i++) {
Composite level1 = new Composite("level1-" + i);
root.add(level1);
for (int j = 0; j < 2; j++) {
Composite level2 = new Composite("level2-" + j);
level1.add(level2);
for (int k = 0; k < 2; k++) {
level2.add(new Leaf("file-" + k, 100));
}
}
}
4.2 缺点详细分析
1. 设计复杂
csharp
// 组件接口需要权衡透明性和安全性
public abstract class Component {
// 透明性方案:所有组件都有add/remove方法
public abstract void add(Component component);
public abstract void remove(Component component);
// 但叶子节点需要抛出异常
// 或者实现空方法
}
2. 类型安全
ini
// 编译时无法检查类型安全
Component file = new Leaf("test.txt", 100);
Component dir = new Composite("docs");
// 编译通过,但运行时抛出异常
file.add(dir); // UnsupportedOperationException
3. 性能开销
java
// 深度遍历的性能问题
public int getTotalSize(Component root) {
int total = 0;
// 递归遍历整个树
if (root instanceof Composite) {
Composite composite = (Composite) root;
for (Component child : composite.getChildren()) {
total += getTotalSize(child); // 递归调用
}
} else {
total += root.getSize();
}
return total;
}
4. 内存占用
csharp
// 每个节点都是对象,占用内存
Composite root = new Composite("root");
for (int i = 0; i < 10000; i++) {
root.add(new Leaf("file" + i, 1024));
}
// 创建了10001个对象
五、使用要点深度解析
5.1 透明性与安全性权衡
透明性方案
csharp
// 所有组件都有相同接口
public abstract class Component {
public abstract void add(Component component);
public abstract void remove(Component component);
public abstract List<Component> getChildren();
// 客户端可以统一调用
public void process(Component component) {
component.add(new Leaf("test", 100)); // 可能抛出异常
}
}
安全性方案
csharp
// 分离接口
public interface Component {
String getName();
int getSize();
}
public interface CompositeComponent extends Component {
void add(Component component);
void remove(Component component);
List<Component> getChildren();
}
public interface LeafComponent extends Component {
// 没有子节点相关方法
}
// 客户端需要检查类型
public void process(Component component) {
if (component instanceof CompositeComponent) {
CompositeComponent composite = (CompositeComponent) component;
composite.add(new Leaf("test", 100));
}
}
5.2 递归实现要点
深度优先遍历
arduino
public class Composite {
private List<Component> children = new ArrayList<>();
public void dfsTraverse(ComponentVisitor visitor) {
visitor.visit(this);
for (Component child : children) {
if (child instanceof Composite) {
((Composite) child).dfsTraverse(visitor);
} else {
visitor.visit(child);
}
}
}
}
广度优先遍历
arduino
public void bfsTraverse(ComponentVisitor visitor) {
Queue<Component> queue = new LinkedList<>();
queue.offer(this);
while (!queue.isEmpty()) {
Component current = queue.poll();
visitor.visit(current);
if (current instanceof Composite) {
Composite composite = (Composite) current;
for (Component child : composite.getChildren()) {
queue.offer(child);
}
}
}
}
5.3 缓存优化
csharp
public class Composite {
private List<Component> children = new ArrayList<>();
private int cachedSize = -1;
public int getSize() {
if (cachedSize < 0) {
cachedSize = 0;
for (Component child : children) {
cachedSize += child.getSize();
}
}
return cachedSize;
}
public void add(Component component) {
children.add(component);
cachedSize = -1; // 清除缓存
}
public void remove(Component component) {
children.remove(component);
cachedSize = -1; // 清除缓存
}
}
5.4 迭代器实现
arduino
public class CompositeIterator implements Iterator<Component> {
private Stack<Iterator<Component>> stack = new Stack<>();
public CompositeIterator(Composite composite) {
stack.push(composite.getChildren().iterator());
}
@Override
public boolean hasNext() {
if (stack.isEmpty()) {
return false;
}
Iterator<Component> iterator = stack.peek();
if (iterator.hasNext()) {
return true;
} else {
stack.pop();
return hasNext();
}
}
@Override
public Component next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Iterator<Component> iterator = stack.peek();
Component component = iterator.next();
if (component instanceof Composite) {
stack.push(((Composite) component).getChildren().iterator());
}
return component;
}
}
六、与其他模式的关系
6.1 组合模式 vs 装饰器模式
scala
// 组合模式:构建树形结构
public class Composite {
private List<Component> children = new ArrayList<>();
// 管理子组件
}
// 装饰器模式:动态添加功能
public abstract class Decorator extends Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
}
6.2 组合模式 vs 迭代器模式
csharp
// 组合模式定义结构
public class Composite {
private List<Component> children = new ArrayList<>();
// ...
}
// 迭代器模式遍历结构
public class CompositeIterator implements Iterator<Component> {
// 遍历组合结构
}
6.3 组合模式 vs 访问者模式
csharp
// 组合模式定义数据结构
public class Composite {
private List<Component> children = new ArrayList<>();
// ...
}
// 访问者模式定义操作
public interface Visitor {
void visit(Leaf leaf);
void visit(Composite composite);
}
// 在组件中接受访问者
public abstract class Component {
public abstract void accept(Visitor visitor);
}
七、实际应用案例
7.1 Spring框架中的应用
scala
// Spring Security中的投票器
public interface AccessDecisionVoter<S> {
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
}
// 投票器管理器(组合模式)
public class AffirmativeBased extends AbstractAccessDecisionManager {
private List<AccessDecisionVoter<?>> decisionVoters;
// 组合多个投票器
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) {
for (AccessDecisionVoter voter : decisionVoters) {
int result = voter.vote(authentication, object, configAttributes);
// 处理投票结果
}
}
}
7.2 Java AWT/Swing
csharp
// Container是组合节点
public class Container extends Component {
private List<Component> components = new ArrayList<>();
public Component add(Component comp) {
components.add(comp);
comp.parent = this;
return comp;
}
public void remove(Component comp) {
components.remove(comp);
comp.parent = null;
}
}
// Component是抽象组件
public abstract class Component {
protected Container parent;
public Container getParent() {
return parent;
}
}
7.3 Apache Wicket
scala
// WebComponent是抽象组件
public abstract class WebComponent extends Component {
private List<Component> children = new ArrayList<>();
public final Component add(Component... children) {
for (Component child : children) {
this.children.add(child);
child.setParent(this);
}
return this;
}
public void visitChildren(IVisitor<Component> visitor) {
for (Component child : children) {
visitor.component(child, this);
}
}
}
八、最佳实践
8.1 设计建议
- 明确区分叶子节点和组合节点
- 考虑使用透明模式或安全模式
- 为常用操作提供缓存支持
- 实现迭代器以支持多种遍历方式
- 考虑添加访问者模式支持复杂操作
8.2 性能优化
- 延迟初始化:在需要时才计算子节点
- 缓存结果:缓存计算密集型操作的结果
- 并行处理:对子树进行并行计算
- 增量更新:只更新受影响的部分
8.3 测试要点
- 测试叶子节点和组合节点的一致性
- 测试递归操作的边界条件
- 测试内存使用情况
- 测试并发安全性
九、总结
组合模式是一种强大的结构型设计模式,特别适合表示树形结构的层次关系。它的核心价值在于:
9.1 核心优势
- 统一接口:客户端可以一致地处理单个对象和组合对象
- 简化代码:减少了客户端代码的复杂性
- 灵活扩展:容易添加新的组件类型
- 层次结构:自然地表示部分-整体层次关系
9.2 适用场景判断
- ✅ 需要表示对象的部分-整体层次结构
- ✅ 希望客户端忽略组合对象与单个对象的差异
- ✅ 需要统一处理单个对象和组合对象
- ✅ 系统有递归结构或树形结构
- ❌ 组件之间差异很大,难以统一接口
- ❌ 性能要求极高,不能接受递归开销
- ❌ 结构简单,不需要复杂的层次关系
9.3 实现要点
- 合理设计组件接口:平衡透明性和安全性
- 实现递归操作:确保正确处理边界条件
- 提供遍历支持:实现迭代器支持多种遍历方式
- 考虑性能优化:使用缓存、延迟初始化等技术
- 确保线程安全:在多线程环境下正确工作
组合模式是构建复杂层次结构的有效工具,正确使用可以显著提高代码的可维护性和扩展性。