【设计模式08】组合模式
【设计模式08】组合模式
一、从业务痛点谈起
还记得我们第一次接手企业组织架构管理系统时的困境吗?一个看似简单的需求:“统计某个部门及其所有下级部门的总人数”,却让团队陷入了代码泥潭。
最初的实现是这样的:先查询一级部门,再循环查询二级部门,接着是三级、四级…代码中充斥着嵌套的if-else和for循环。更糟糕的是,当组织架构层级发生变化时,代码就得重写。某大型集团的组织架构甚至达到了8层,维护这样的代码简直是噩梦。
这时候,我们需要一种设计模式,能够统一处理单个对象和组合对象,让客户端无需关心处理的是叶子节点还是组合节点——这就是组合模式的价值所在。
二、组合模式的核心思想
2.1 定义与本质
组合模式(Composite Pattern):将对象组合成树形结构以表示"部分-整体"的层次结构,使客户端对单个对象和组合对象的使用具有一致性。
打个生活化的比方:组合模式就像俄罗斯套娃,每个套娃既可以是独立的个体,也可以包含其他套娃。无论是操作单个套娃还是一整套,方式都是一样的。
2.2 核心结构解析
组合模式的三个核心角色:
- Component(抽象组件):定义叶子和容器的共同接口
- Leaf(叶子组件):树形结构的叶子节点,没有子节点
- Composite(容器组件):可以包含子节点的容器对象
2.3 适用场景分析
场景特征 | 具体描述 | 典型案例 |
---|---|---|
树形结构 | 数据本身具有层次关系 | 组织架构、文件系统、菜单管理 |
统一处理 | 希望统一处理个体和组合 | 图形编辑器、UI组件树 |
递归操作 | 需要递归遍历整个结构 | 权限管理、规则引擎 |
动态扩展 | 结构层级可能动态变化 | 商品分类、地区管理 |
三、实现思路与关键代码
3.1 核心逻辑实现
让我们通过企业组织架构的例子,展示组合模式的几种实现方式:
方式一:透明式组合模式(推荐)
// 抽象组件:定义所有组件的公共接口
public abstract class OrganizationComponent {protected String name;protected String description;public OrganizationComponent(String name, String description) {this.name = name;this.description = description;}// 默认实现,叶子节点会抛出异常public void add(OrganizationComponent component) {throw new UnsupportedOperationException("不支持添加操作");}public void remove(OrganizationComponent component) {throw new UnsupportedOperationException("不支持删除操作");}// 抽象方法,强制子类实现public abstract void display(int depth);public abstract int calculateEmployeeCount();
}// 叶子节点:员工
public class Employee extends OrganizationComponent {private String position;public Employee(String name, String position) {super(name, position);this.position = position;}@Overridepublic void display(int depth) {// 打印缩进,体现层级关系System.out.println("-".repeat(depth) + " 员工:" + name + ",职位:" + position);}@Overridepublic int calculateEmployeeCount() {return 1; // 员工算1个人}
}// 容器节点:部门
public class Department extends OrganizationComponent {// 存储子组件private List<OrganizationComponent> components = new ArrayList<>();public Department(String name, String description) {super(name, description);}@Overridepublic void add(OrganizationComponent component) {components.add(component);}@Overridepublic void remove(OrganizationComponent component) {components.remove(component);}@Overridepublic void display(int depth) {System.out.println("-".repeat(depth) + " 部门:" + name);// 递归显示所有子组件for (OrganizationComponent component : components) {component.display(depth + 2);}}@Overridepublic int calculateEmployeeCount() {int count = 0;// 递归计算所有子组件的人数for (OrganizationComponent component : components) {count += component.calculateEmployeeCount();}return count;}
}
方式二:安全式组合模式
// 抽象组件:只定义公共方法
public abstract class OrganizationUnit {protected String name;public OrganizationUnit(String name) {this.name = name;}// 只定义所有组件都有的方法public abstract void display(int depth);public abstract int getStaffCount();
}// 容器接口:定义容器特有的方法
public interface IContainer {void add(OrganizationUnit unit);void remove(OrganizationUnit unit);List<OrganizationUnit> getChildren();
}// 部门实现
public class DepartmentSafe extends OrganizationUnit implements IContainer {private List<OrganizationUnit> children = new ArrayList<>();@Overridepublic void add(OrganizationUnit unit) {children.add(unit);}@Overridepublic void remove(OrganizationUnit unit) {children.remove(unit);}@Overridepublic List<OrganizationUnit> getChildren() {return children;}@Overridepublic void display(int depth) {System.out.println("-".repeat(depth) + " 部门:" + name);for (OrganizationUnit child : children) {child.display(depth + 2);}}@Overridepublic int getStaffCount() {return children.stream().mapToInt(OrganizationUnit::getStaffCount).sum();}
}// 员工实现
public class EmployeeSafe extends OrganizationUnit {@Overridepublic void display(int depth) {System.out.println("-".repeat(depth) + " 员工:" + name);}@Overridepublic int getStaffCount() {return 1;}
}
方式三:带缓存优化的组合模式
public class CachedDepartment extends OrganizationComponent {private List<OrganizationComponent> components = new ArrayList<>();private Integer cachedEmployeeCount; // 缓存员工数量private boolean isDirty = true; // 标记缓存是否失效@Overridepublic void add(OrganizationComponent component) {components.add(component);isDirty = true; // 添加后缓存失效}@Overridepublic void remove(OrganizationComponent component) {components.remove(component);isDirty = true; // 删除后缓存失效}@Overridepublic int calculateEmployeeCount() {if (isDirty || cachedEmployeeCount == null) {// 重新计算并缓存cachedEmployeeCount = components.stream().mapToInt(OrganizationComponent::calculateEmployeeCount).sum();isDirty = false;}return cachedEmployeeCount;}
}
3.2 不同实现方式对比
实现方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
透明式 | 客户端调用简单统一 | 叶子节点包含无意义方法 | 结构相对稳定,强调使用便利性 |
安全式 | 接口定义清晰,类型安全 | 客户端需要区分类型 | 注重类型安全,结构复杂多变 |
带缓存 | 性能优化,避免重复计算 | 实现复杂,需要维护缓存一致性 | 大规模数据,频繁查询场景 |
四、企业级应用案例
4.1 案例一:美团组织架构权限管理系统
背景与问题:
美团的组织架构极其复杂,包含总部、城市分公司、业务线、部门等多个层级。权限管理需求:
- 上级部门自动拥有下级部门的所有权限
- 支持按部门批量授权
- 能够快速查询某员工的所有权限(包括继承的)
模式应用方案:
public class PermissionComponent {// 权限树节点public abstract class PermissionNode {protected String nodeId;protected Set<String> directPermissions = new HashSet<>();// 获取所有权限(包括继承的)public abstract Set<String> getAllPermissions();// 检查是否有某权限public boolean hasPermission(String permission) {return getAllPermissions().contains(permission);}}// 部门节点public class DepartmentNode extends PermissionNode {private List<PermissionNode> children = new ArrayList<>();private Department department;@Overridepublic Set<String> getAllPermissions() {Set<String> allPermissions = new HashSet<>(directPermissions);// 递归收集所有子部门的权限for (PermissionNode child : children) {allPermissions.addAll(child.getAllPermissions());}return allPermissions;}// 批量授权public void grantPermissionToAll(String permission) {this.directPermissions.add(permission);// 自动传播到所有子节点for (PermissionNode child : children) {if (child instanceof DepartmentNode) {((DepartmentNode) child).grantPermissionToAll(permission);}}}}// 员工节点public class EmployeeNode extends PermissionNode {private Employee employee;private DepartmentNode department; // 所属部门@Overridepublic Set<String> getAllPermissions() {Set<String> allPermissions = new HashSet<>(directPermissions);// 继承部门权限if (department != null) {allPermissions.addAll(department.getAllPermissions());}return allPermissions;}}
}
实施效果:
- 权限继承关系清晰,维护成本降低60%
- 批量授权效率提升10倍
- 支持灵活的组织架构调整,代码零修改
4.2 案例二:京东商品分类体系
背景与问题:
京东的商品分类体系包含数万个类目,层级深度不一(3-7层)。需求包括:
- 统计某分类下的所有商品数量
- 批量调整分类下所有商品的属性
- 生成分类面包屑导航
模式应用方案:
public abstract class CategoryComponent {protected Long categoryId;protected String categoryName;protected Integer level;// 生成面包屑public abstract String generateBreadcrumb();// 统计商品数量public abstract int countProducts();// 批量操作public abstract void batchUpdate(Map<String, Object> attributes);
}public class Category extends CategoryComponent {private List<CategoryComponent> subCategories = new ArrayList<>();private CategoryComponent parent;@Overridepublic String generateBreadcrumb() {if (parent == null) {return categoryName;}return parent.generateBreadcrumb() + " > " + categoryName;}@Overridepublic int countProducts() {// 使用并行流提升性能return subCategories.parallelStream().mapToInt(CategoryComponent::countProducts).sum();}@Overridepublic void batchUpdate(Map<String, Object> attributes) {// 异步批量更新CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {updateSelf(attributes);// 递归更新子分类List<CompletableFuture<Void>> futures = subCategories.stream().map(sub -> CompletableFuture.runAsync(() -> sub.batchUpdate(attributes))).collect(Collectors.toList());CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();});}
}
五、模式深度剖析
5.1 优势与局限
优势:
- 简化客户端代码:客户端无需关心处理的是单个对象还是组合对象
- 扩展性强:易于增加新的节点类型
- 符合开闭原则:对扩展开放,对修改关闭
- 递归处理自然:天然支持递归操作
局限:
- 设计复杂度:可能使设计变得过于一般化
- 类型安全问题:透明式组合中,叶子节点包含无意义的方法
- 性能考虑:深层递归可能导致栈溢出
- 难以限制组件类型:难以限制容器中的组件类型
5.2 线程安全考量
public class ThreadSafeDepartment extends OrganizationComponent {// 使用线程安全的集合private final CopyOnWriteArrayList<OrganizationComponent> components = new CopyOnWriteArrayList<>();// 使用读写锁优化性能private final ReadWriteLock lock = new ReentrantReadWriteLock();@Overridepublic void add(OrganizationComponent component) {lock.writeLock().lock();try {components.add(component);} finally {lock.writeLock().unlock();}}@Overridepublic int calculateEmployeeCount() {lock.readLock().lock();try {return components.stream().mapToInt(OrganizationComponent::calculateEmployeeCount).sum();} finally {lock.readLock().unlock();}}
}
5.3 性能影响分析
操作类型 | 时间复杂度 | 优化策略 |
---|---|---|
遍历 | O(n) | 使用并行流、分页加载 |
查找特定节点 | O(n) | 建立索引Map、使用缓存 |
添加/删除 | O(1) | 使用高效数据结构 |
统计计算 | O(n) | 缓存计算结果、增量更新 |
六、框架中的应用
6.1 Spring中的组合模式
Spring中多处使用了组合模式:
// Spring的CompositeMessageSource
public class CompositeMessageSource implements MessageSource {private final List<MessageSource> messageSources = new ArrayList<>();public void addMessageSource(MessageSource messageSource) {this.messageSources.add(messageSource);}@Overridepublic String getMessage(String code, Object[] args, Locale locale) {for (MessageSource messageSource : messageSources) {try {return messageSource.getMessage(code, args, locale);} catch (NoSuchMessageException ex) {// 继续尝试下一个MessageSource}}throw new NoSuchMessageException(code, locale);}
}// Spring Security的投票机制
public class AffirmativeBased extends AbstractAccessDecisionManager {public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) {// 遍历所有投票器(组合模式)for (AccessDecisionVoter voter : getDecisionVoters()) {int result = voter.vote(authentication, object, configAttributes);if (result == AccessDecisionVoter.ACCESS_GRANTED) {return; // 只要有一个同意就通过}}}
}
6.2 MyBatis中的应用
// MyBatis的SqlNode体系
public interface SqlNode {boolean apply(DynamicContext context);
}public class MixedSqlNode implements SqlNode {private final List<SqlNode> contents;@Overridepublic boolean apply(DynamicContext context) {// 组合多个SqlNodecontents.forEach(node -> node.apply(context));return true;}
}
七、与相似模式的辨析
7.1 组合模式 vs 装饰器模式
对比维度 | 组合模式 | 装饰器模式 |
---|---|---|
设计目的 | 表示"部分-整体"层次 | 动态添加职责 |
结构关系 | 树形结构 | 链式结构 |
对象数量 | 可包含多个子对象 | 一般包装一个对象 |
使用重点 | 统一处理个体和组合 | 透明地扩展功能 |
7.2 组合模式 vs 迭代器模式
组合模式经常与迭代器模式配合使用:
public class DepartmentWithIterator extends Department implements Iterable<OrganizationComponent> {@Overridepublic Iterator<OrganizationComponent> iterator() {return new DepartmentIterator();}private class DepartmentIterator implements Iterator<OrganizationComponent> {private Stack<Iterator<OrganizationComponent>> stack = new Stack<>();public DepartmentIterator() {stack.push(components.iterator());}@Overridepublic boolean hasNext() {if (stack.empty()) return false;Iterator<OrganizationComponent> iterator = stack.peek();if (!iterator.hasNext()) {stack.pop();return hasNext();}return true;}@Overridepublic OrganizationComponent next() {if (hasNext()) {Iterator<OrganizationComponent> iterator = stack.peek();OrganizationComponent component = iterator.next();if (component instanceof Department) {stack.push(((Department) component).components.iterator());}return component;}return null;}}
}
八、实践启示
通过对组合模式的深入剖析和实战应用,我们得到以下核心启示:
-
递归思维的力量:组合模式将递归思维优雅地融入面向对象设计中,让我们能够用统一的方式处理复杂的树形结构。这启发我们在面对层次性数据时,要善于发现和利用递归特性。
-
接口统一的价值:通过统一叶子节点和容器节点的接口,组合模式极大地简化了客户端代码。这提醒我们,良好的抽象能够隐藏复杂性,提供简洁的使用体验。
-
性能与设计的权衡:在实际应用中,我们需要在设计的优雅性和性能之间找到平衡点。缓存机制、并行处理、懒加载等优化手段都是必要的补充。
-
安全性不应被忽视:选择透明式还是安全式组合,取决于我们对类型安全的重视程度。在关键业务系统中,类型安全往往比使用便利性更重要。
-
模式组合的威力:组合模式与访问者模式、迭代器模式、责任链模式等配合使用,能够构建出功能强大且灵活的系统架构。
记住,组合模式不仅仅是一种设计模式,更是一种思维方式——当我们面对"整体与部分"的关系时,要思考如何让它们和谐共存,统一处理。