设计模式详解-组合模式

设计模式详解:组合模式

一、模式概述

组合模式(Composite Pattern)是结构型设计模式中最具递归美感的模式,其核心意图在于将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性。这一模式让客户端能够用统一的接口处理原子对象和复合对象,无需区分二者,从而简化了树形结构的操作逻辑。

组合模式的命名直接揭示了其本质——"组合"即部分构成整体,整体又成为更大整体的组成部分。自然界中,细胞组成组织,组织构成器官,器官形成系统;文件系统中,文件组成目录,目录嵌套目录,形成层级结构;组织架构中,员工向经理汇报,经理向总监汇报,总监向副总裁汇报。这些场景的共同特征是递归的层次结构,而组合模式正是对此类结构的软件抽象。

组合模式的深层价值在于透明性与一致性的统一。没有组合模式时,客户端必须编写条件分支来区分处理叶子节点和分支节点,代码充斥着if (node.isLeaf())的判断。组合模式通过让二者实现同一接口,消除了这种区分,使递归算法得以优雅表达。

二、模式结构

组合模式包含三个核心角色,形成递归的树形结构:

组件接口(Component):定义叶子节点和复合节点的共同接口,声明访问和管理子组件的方法。可为接口或抽象类,根据需要提供默认实现。

叶子节点(Leaf):表示树中的原子对象,没有子节点。实现组件接口的基本行为。

复合节点(Composite):表示有子节点的分支对象。实现组件接口,并存储子组件集合,定义增删查等管理操作。在调用自身行为时,通常递归委托给子组件。

组合模式的关键设计决策在于组件接口的丰富程度。安全版本仅在Composite中定义子组件管理方法,客户端需类型转换才能调用;透明版本在Component中统一声明所有方法,Leaf对管理方法提供空实现或抛异常。透明版本牺牲类型安全换取接口一致性,是更常见的实践。

三、深度案例:企业级权限管理系统

以下展示一个真实场景下的组合模式应用——支持多维度、多层级、动态继承的企业级权限管理系统,涵盖组织架构、资源目录、角色继承、审批流程等复杂树形结构。

3.1 组件接口:权限节点抽象

java
/** * 组件接口:权限模型中的可授权实体 * 采用透明组合设计:所有操作统一声明,叶子节点提供空实现 */ public interface Securable { // ========== 身份标识 ========== String getId(); String getName(); SecurableType getType(); // ========== 权限判定(核心行为) ========== /** * 判定主体对此对象是否拥有指定权限 */ boolean checkPermission(Subject subject, Permission permission); /** * 获取主体在此对象上的有效权限集合 * 考虑直接授权、继承、角色叠加等所有因素 */ Set<Permission> resolveEffectivePermissions(Subject subject); // ========== 树形结构操作(透明组合) ========== /** * 添加子节点(叶子节点空实现) */ default void addChild(Securable child) { throw new UnsupportedOperationException("叶子节点不支持添加子节点"); } /** * 移除子节点(叶子节点空实现) */ default void removeChild(Securable child) { throw new UnsupportedOperationException("叶子节点不支持移除子节点"); } /** * 获取子节点列表(叶子节点返回空列表) */ default List<Securable> getChildren() { return Collections.emptyList(); } /** * 获取父节点 */ Securable getParent(); /** * 设置父节点 */ void setParent(Securable parent); // ========== 路径与层级操作 ========== /** * 获取从根节点到当前节点的完整路径 */ default List<Securable> getPath() { List<Securable> path = new ArrayList<>(); Securable current = this; while (current != null) { path.add(0, current); // 头插法保持根到叶的顺序 current = current.getParent(); } return path; } /** * 获取层级深度(根节点为0) */ default int getDepth() { return getPath().size() - 1; } /** * 获取根节点 */ default Securable getRoot() { List<Securable> path = getPath(); return path.isEmpty() ? null : path.get(0); } // ========== 遍历操作 ========== /** * 前序遍历:先访问自身,再递归访问子节点 */ default void preOrder(Consumer<Securable> visitor) { visitor.accept(this); for (Securable child : getChildren()) { child.preOrder(visitor); } } /** * 后序遍历:先递归访问子节点,再访问自身 */ default void postOrder(Consumer<Securable> visitor) { for (Securable child : getChildren()) { child.postOrder(visitor); } visitor.accept(this); } /** * 按条件查找所有匹配的子孙节点 */ default List<Securable> findAll(Predicate<Securable> predicate) { List<Securable> result = new ArrayList<>(); preOrder(node -> { if (predicate.test(node)) { result.add(node); } }); return result; } }

3.2 叶子节点:原子权限实体

java
/** * 叶子节点:具体资源(文件、菜单、API端点、数据字段等) */ public class Resource implements Securable { private String id; private String name; private ResourceType resourceType; // FILE, MENU, API, DATA_FIELD等 private String resourceLocator; // 资源定位符:路径、URL、表名.字段名等 private Securable parent; // 直接授权规则:谁对此资源有什么权限 private Map<String, Set<Permission>> directGrants = new HashMap<>(); // 资源特定的权限集合(不同资源类型有不同权限) private Set<Permission> supportedPermissions; // 数据范围规则(用于数据权限) private DataScopeRule dataScopeRule; public Resource(String id, String name, ResourceType type, String locator) { this.id = id; this.name = name; this.resourceType = type; this.resourceLocator = locator; } @Override public String getId() { return id; } @Override public String getName() { return name; } @Override public SecurableType getType() { return SecurableType.RESOURCE; } @Override public Securable getParent() { return parent; } @Override public void setParent(Securable parent) { this.parent = parent; } @Override public boolean checkPermission(Subject subject, Permission permission) { // 1. 检查直接授权 Set<Permission> direct = directGrants.get(subject.getId()); if (direct != null && direct.contains(permission)) { return true; } // 2. 检查角色授权(通过Subject的角色间接获得) for (Role role : subject.getRoles()) { if (role.checkPermission(this, permission)) { return true; } } // 3. 检查继承授权(从父资源继承) if (parent != null && inheritsFromParent()) { return parent.checkPermission(subject, permission); } return false; } @Override public Set<Permission> resolveEffectivePermissions(Subject subject) { Set<Permission> effective = new HashSet<>(); // 直接授权 Set<Permission> direct = directGrants.get(subject.getId()); if (direct != null) effective.addAll(direct); // 角色授权 for (Role role : subject.getRoles()) { effective.addAll(role.getPermissions(this)); } // 继承授权 if (parent != null && inheritsFromParent()) { effective.addAll(parent.resolveEffectivePermissions(subject)); } // 过滤为资源支持的权限 effective.retainAll(supportedPermissions); return effective; } // 资源特定方法 public void grant(String subjectId, Permission... permissions) { directGrants.computeIfAbsent(subjectId, k -> new HashSet<>()) .addAll(Arrays.asList(permissions)); } public void revoke(String subjectId, Permission... permissions) { Set<Permission> grants = directGrants.get(subjectId); if (grants != null) { grants.removeAll(Arrays.asList(permissions)); } } public boolean inheritsFromParent() { // 默认继承,可被配置覆盖 return dataScopeRule == null || dataScopeRule.isInheritEnabled(); } // 数据范围计算:返回此资源对特定主体的可见数据范围 public DataScope computeDataScope(Subject subject) { if (dataScopeRule == null) { return DataScope.all(); } return dataScopeRule.evaluate(subject, this); } } /** * 叶子节点:用户/主体 * 在组织架构树中作为叶子,但自身可拥有角色 */ public class User implements Securable { private String id; private String name; private String username; private Securable parent; // 所属部门 private Set<Role> directRoles = new HashSet<>(); private boolean enabled; private LocalDateTime validFrom; private LocalDateTime validUntil; @Override public boolean checkPermission(Subject subject, Permission permission) { // 用户节点本身不直接承载权限判定逻辑 // 权限通过其角色和所属组织架构间接解析 throw new UnsupportedOperationException("请通过Subject封装调用"); } @Override public Set<Permission> resolveEffectivePermissions(Subject subject) { // 汇总所有角色的权限,考虑角色冲突消解策略 Map<String, Permission> merged = new HashMap<>(); for (Role role : getAllEffectiveRoles()) { for (Permission p : role.getGlobalPermissions()) { // 冲突消解:显式拒绝优先 if (p.getEffect() == Effect.DENY) { merged.put(p.getAction() + ":" + p.getResourcePattern(), p); } else if (!merged.containsKey(p.getAction() + ":" + p.getResourcePattern())) { merged.put(p.getAction() + ":" + p.getResourcePattern(), p); } } } return new HashSet<>(merged.values()); } // 获取所有生效角色:直接角色 + 继承自组织架构的角色 + 继承自岗位的角色 public Set<Role> getAllEffectiveRoles() { Set<Role> roles = new HashSet<>(directRoles); // 从父部门继承 if (parent instanceof Department) { roles.addAll(((Department) parent).getInheritedRoles()); } // 从岗位继承 for (Position position : positions) { roles.addAll(position.getAttachedRoles()); } return roles; } // 其余实现... }

3.3 复合节点:组织架构与角色树

java
/** * 复合节点:部门/组织单元 * 可包含子部门、用户、岗位等 */ public class Department implements Securable { private String id; private String name; private String code; // 组织编码,如"1001.2003.5002" private DepartmentType type; // GROUP, COMPANY, BRANCH, TEAM等 private Securable parent; private List<Securable> children = new ArrayList<>(); // 部门级别默认角色(所有成员自动继承) private Set<Role> defaultRoles = new HashSet<>(); // 权限继承策略 private InheritanceStrategy inheritanceStrategy = InheritanceStrategy.CASCADE; @Override public String getId() { return id; } @Override public String getName() { return name; } @Override public SecurableType getType() { return SecurableType.DEPARTMENT; } @Override public Securable getParent() { return parent; } @Override public void setParent(Securable parent) { this.parent = parent; } // ========== 组合核心:子节点管理 ========== @Override public void addChild(Securable child) { // 类型校验 if (!isValidChildType(child.getType())) { throw new IllegalArgumentException( "部门不能包含类型为" + child.getType() + "的子节点"); } // 防止循环引用 if (this.equals(child) || isDescendantOf(child)) { throw new IllegalArgumentException("不能形成循环引用"); } // 移除旧父关系 if (child.getParent() != null) { child.getParent().removeChild(child); } children.add(child); child.setParent(this); // 触发组织变更事件 publishEvent(new OrgStructureChangedEvent(this, child, ChangeType.ADD)); } @Override public void removeChild(Securable child) { if (children.remove(child)) { child.setParent(null); publishEvent(new OrgStructureChangedEvent(this, child, ChangeType.REMOVE)); } } @Override public List<Securable> getChildren() { // 返回不可修改视图,强制通过组合方法操作 return Collections.unmodifiableList(children); } // ========== 权限判定(递归委托) ========== @Override public boolean checkPermission(Subject subject, Permission permission) { // 部门的权限判定:检查是否有子资源满足条件 // 用于"部门管理员"场景:对部门有管理权限即对部门内所有资源有权限 // 1. 检查部门级别的直接授权 if (hasDirectGrant(subject, permission)) { return true; } // 2. 根据继承策略,可能委托给子节点 switch (inheritanceStrategy) { case CASCADE: // 级联:任一子节点有权限即认为有权限 return children.stream() .anyMatch(child -> child.checkPermission(subject, permission)); case STRICT: // 严格:必须所有子节点都有权限 return children.stream() .allMatch(child -> child.checkPermission(subject, permission)); case NONE: // 不继承:仅检查部门自身 return false; default: return false; } } @Override public Set<Permission> resolveEffectivePermissions(Subject subject) { // 聚合所有子节点的有效权限 Set<Permission> union = new HashSet<>(); // 部门默认角色 for (Role role : defaultRoles) { if (subject.hasRole(role)) { union.addAll(role.getGlobalPermissions()); } } // 递归聚合子节点 for (Securable child : children) { union.addAll(child.resolveEffectivePermissions(subject)); } return union; } // ========== 部门特定操作 ========== /** * 获取所有子孙用户(扁平化) */ public List<User> getAllUsers() { List<User> users = new ArrayList<>(); preOrder(node -> { if (node instanceof User) { users.add((User) node); } }); return users; } /** * 获取直接用户(仅一层) */ public List<User> getDirectUsers() { return children.stream() .filter(c -> c instanceof User) .map(c -> (User) c) .collect(Collectors.toList()); } /** * 获取所有子部门 */ public List<Department> getSubDepartments() { List<Department> depts = new ArrayList<>(); preOrder(node -> { if (node instanceof Department && node != this) { depts.add((Department) node); } }); return depts; } /** * 计算部门统计:人数、角色分布、权限饱和度等 */ public DepartmentStatistics computeStatistics() { DepartmentStatistics stats = new DepartmentStatistics(); postOrder(node -> { if (node instanceof User) { stats.incrementUserCount(); stats.mergeRoleDistribution(((User) node).getAllEffectiveRoles()); } else if (node instanceof Department) { // 后序遍历确保子部门统计已计算 stats.mergeChildStats(((Department) node).computeStatistics()); } }); return stats; } /** * 批量调整组织架构:移动整个子树 */ public void moveSubtree(Department targetParent) { if (this.equals(targetParent) || isDescendantOf(targetParent)) { throw new IllegalArgumentException("非法的移动操作"); } Securable oldParent = this.parent; oldParent.removeChild(this); targetParent.addChild(this); // 级联更新编码 regenerateCodes(); publishEvent(new DepartmentMovedEvent(this, oldParent, targetParent)); } // 辅助方法 private boolean isDescendantOf(Securable ancestor) { Securable current = this.parent; while (current != null) { if (current.equals(ancestor)) return true; current = current.getParent(); } return false; } private boolean isValidChildType(SecurableType type) { return type == SecurableType.DEPARTMENT || type == SecurableType.USER || type == SecurableType.POSITION; } private void regenerateCodes() { String parentCode = parent instanceof Department ? ((Department) parent).getCode() : ""; this.code = parentCode + "." + id; // 递归更新所有子部门编码 children.stream() .filter(c -> c instanceof Department) .forEach(c -> ((Department) c).regenerateCodes()); } } /** * 复合节点:角色组(角色可继承形成层级) */ public class RoleGroup implements Securable { private String id; private String name; private Securable parent; // 父角色或角色组 private List<Securable> children = new ArrayList<>(); // 子角色或子角色组 // 权限规则:此角色组定义的权限模板 private Set<PermissionRule> permissionRules = new HashSet<>(); // 角色冲突消解策略 private ConflictResolution conflictResolution = ConflictResolution.DENY_OVERRIDES; @Override public boolean checkPermission(Subject subject, Permission permission) { // 角色组的权限判定:评估所有规则,应用冲突消解 List<PermissionRule> matchingRules = permissionRules.stream() .filter(r -> r.matches(permission)) .collect(Collectors.toList()); if (matchingRules.isEmpty() && parent != null) { // 委托给父角色 return parent.checkPermission(subject, permission); } return resolveConflict(matchingRules).stream() .anyMatch(r -> r.getEffect() == Effect.ALLOW); } @Override public Set<Permission> resolveEffectivePermissions(Subject subject) { // 递归收集所有子角色的权限,应用冲突消解 Map<String, Permission> merged = new HashMap<>(); // 自身规则 for (PermissionRule rule : permissionRules) { merged.put(rule.getKey(), rule.toPermission()); } // 子角色规则(可能覆盖) for (Securable child : children) { for (Permission p : child.resolveEffectivePermissions(subject)) { Permission existing = merged.get(p.getKey()); if (existing == null || shouldOverride(existing, p)) { merged.put(p.getKey(), p); } } } return new HashSet<>(merged.values()); } // 子角色管理 @Override public void addChild(Securable child) { if (!(child instanceof Role || child instanceof RoleGroup)) { throw new IllegalArgumentException("角色组只能包含角色或角色组"); } children.add(child); child.setParent(this); } @Override public void removeChild(Securable child) { children.remove(child); child.setParent(null); } @Override public List<Securable> getChildren() { return Collections.unmodifiableList(children); } // 其余实现... }

3.4 迭代器模式扩展:安全遍历

java
/** * 组合模式的安全迭代器:防止遍历过程中结构修改导致的不一致 */ public class SafeSecurableIterator implements Iterator<Securable> { private final Iterator<Securable> snapshotIterator; public SafeSecurableIterator(Securable root) { // 创建快照,避免遍历过程中的并发修改 List<Securable> snapshot = new ArrayList<>(); root.preOrder(snapshot::add); this.snapshotIterator = snapshot.iterator(); } @Override public boolean hasNext() { return snapshotIterator.hasNext(); } @Override public Securable next() { return snapshotIterator.next(); } } /** * 懒加载迭代器:适用于超大树,避免一次性加载全部节点 */ public class LazySecurableIterator implements Iterator<Securable> { private final Deque<Iterator<Securable>> stack = new ArrayDeque<>(); public LazySecurableIterator(Securable root) { stack.push(List.of(root).iterator()); } @Override public boolean hasNext() { while (!stack.isEmpty() && !stack.peek().hasNext()) { stack.pop(); } return !stack.isEmpty(); } @Override public Securable next() { if (!hasNext()) throw new NoSuchElementException(); Securable current = stack.peek().next(); // 将子节点迭代器压栈,延迟遍历 if (!current.getChildren().isEmpty()) { stack.push(current.getChildren().iterator()); } return current; } }

四、组合模式的高级主题

4.1 访问者模式集成

当需要对组合结构执行多种异构操作时,避免在每个节点类中增加方法:

java
public interface SecurableVisitor { void visitResource(Resource resource); void visitDepartment(Department department); void visitUser(User user); void visitRoleGroup(RoleGroup roleGroup); } public class PermissionAuditVisitor implements SecurableVisitor { private final AuditReport report = new AuditReport(); @Override public void visitResource(Resource resource) { // 检查资源是否有孤立授权(授予已离职用户) for (String subjectId : resource.getDirectGrantSubjects()) { if (!userService.existsAndActive(subjectId)) { report.addFinding(new OrphanGrantFinding(resource, subjectId)); } } } @Override public void visitDepartment(Department department) { // 检查部门是否形成权限黑洞(子节点权限超过父节点) // ... } // 其余visit方法... public AuditReport getReport() { return report; } } // 在Securable接口中增加accept方法 default void accept(SecurableVisitor visitor) { if (this instanceof Resource) visitor.visitResource((Resource) this); else if (this instanceof Department) visitor.visitDepartment((Department) this); // ... }

4.2 备忘录模式集成:快照与回滚

java
public class OrgStructureMemento { private final String serializedTree; private final Instant capturedAt; public static OrgStructureMemento capture(Securable root) { return new OrgStructureMemento(serialize(root), Instant.now()); } public void restore(Securable root) { // 反序列化并重建树结构 Securable restored = deserialize(serializedTree); // 替换root的子节点... } }

五、组合模式与相关模式的辨析

组合模式 vs 装饰器模式:二者均基于递归组合,但意图迥异。装饰器为对象添加新职责,保持接口不变;组合模式统一处理部分与整体,形成树形结构。装饰器是"增强功能",组合是"统一结构"。

组合模式 vs 享元模式:大组合结构中,大量相似的叶子节点可通过享元共享,减少内存占用。例如文件系统中,大量空文件可共享同一个空内容对象。

组合模式 vs 迭代器模式:组合提供结构,迭代器提供遍历算法。二者常结合使用,以多种顺序(前序、后序、层序)遍历组合结构。

六、现代架构中的组合思想

React组件树:函数组件和类组件形成统一的组件接口,可嵌套组合,props透传实现数据流,正是组合模式的现代前端实践。

Kubernetes资源模型:Pod、ReplicaSet、Deployment、Service等资源对象形成层级组合,kubectl统一操作,无需区分原子资源和复合资源。

Terraform资源配置:模块可嵌套模块,资源可嵌入模块,形成基础设施即代码的组合结构。

权限即代码(RBAC/ABAC):Open Policy Agent的Rego策略、AWS IAM策略,均以组合方式构建复杂的授权规则。

七、设计陷阱与规避策略

陷阱一:组合过深导致性能问题

递归操作在深层组合上可能栈溢出或耗时过长。解决方案:限制组合深度(如组织架构不超过10层)、使用迭代替代递归、或采用路径枚举/嵌套集等数据库优化技术。

陷阱二:透明接口的非法操作

叶子节点调用addChild时抛异常,可能在运行时才暴露问题。解决方案:在开发阶段使用静态分析工具、或提供类型安全的包装方法。

陷阱三:循环引用

父节点误设为自身子孙,导致无限递归。解决方案:在addChild中显式检测,或使用有向图算法验证无环性。

八、结语

组合模式是处理树形层次结构的经典解决方案,其价值在于以一致的接口消解部分与整体的差异,使递归算法得以优雅表达。在权限管理、组织架构、文件系统、UI组件等天然具有层级结构的领域,组合模式是架构设计的基石。理解其透明性设计、掌握与访问者、备忘录等模式的协作、警惕深度与循环等工程陷阱,是运用好这一模式的关键。组合模式不仅是一种代码结构,更是一种认知工具——它教会我们以递归的视角审视世界,在部分与整体的统一中发现简洁之美。

返回知识中心