设计模式——组合设计模式(结构型)
摘要
组合设计模式是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构,使客户端对单个对象和组合对象具有一致的访问方式。它包含抽象组件、叶子节点和组合节点,具有统一处理、支持递归结构和易扩展等优点,适用树形结构场景,如组织架构、菜单、规则集等。
1. 组合设计模式定义
组合设计模式(Composite Pattern)是一种结构型设计模式,它用于将对象组合成树形结构以表示“部分-整体”的层次结构,使得客户端对单个对象和组合对象具有一致的访问方式。组合模式允许你将对象组合成树形结构来表示“整体/部分”层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
1.1.1. ✅ UML 结构图(简化):
Component(抽象组件)↑┌──────┴──────┐
Leaf(叶子节点) Composite(容器/组合节点)
- Component:定义组合中对象的接口,可以是抽象类或接口。
- Leaf:叶子节点,表示树结构中的子节点,不能再包含其他子对象。
- Composite:组合节点,表示可以拥有子节点的对象,它实现了 Component 接口,并维护子组件集合。
1.1.2. ✅ 举例说明(风控场景类比):
比如在风控系统中,有一套规则系统:
- 单一规则(如“手机号黑名单检查”)是
Leaf
; - 组合规则(如“黑名单检查 + 地域异常组合规则”)是
Composite
; - 客户端调用统一的
evaluate()
方法即可,不需要关心规则是单一还是组合。
1.1.3. ✅ 核心优势:
优点 | 说明 |
统一处理单个和组合对象 | 客户端代码一致,不用区别对待 |
支持递归结构 | 适用于树形结构,如组织架构、菜单、规则集 |
易于扩展 | 增加新的 |
2. 组合设计模式结构
2.1. 组合设计模式类图
结构说明
- 组件 (Component) 接口描述了树中简单项目和复杂项目所共有的操作。
- 叶节点 (Leaf) 是树的基本结构, 它不包含子项目。一般情况下, 叶节点最终会完成大部分的实际工作, 因为它们无法将工作指派给其他部分。
- 容器 (Container)——又名 “组合 (Composite)”——是包含叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的具体类, 它只通过通用的组件接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。
- 客户端 (Client) 通过组件接口与所有项目交互。 因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。
2.2. 组合模式时序图
3. 组合设计模式实现方式
将单个对象(叶子节点)和组合对象(容器节点)统一抽象为一个组件接口,客户端无需关心操作对象是单一元素还是组合结构,均通过统一接口访问。
3.1. Step 1:定义统一接口 Component
public interface Component {void operation();
}
3.2. Step 2:实现叶子节点 Leaf
public class Leaf implements Component {private String name;public Leaf(String name) {this.name = name;}@Overridepublic void operation() {System.out.println("Leaf [" + name + "] 执行操作");}
}
3.3. Step 3:实现组合节点 Composite
import java.util.ArrayList;
import java.util.List;public class Composite implements Component {private String name;private List<Component> children = new ArrayList<>();public Composite(String name) {this.name = name;}public void add(Component component) {children.add(component);}public void remove(Component component) {children.remove(component);}@Overridepublic void operation() {System.out.println("Composite [" + name + "] 开始操作");for (Component child : children) {child.operation();}}
}
3.4. Step 4:客户端使用(Client)
public class Client {public static void main(String[] args) {Leaf leaf1 = new Leaf("规则 A");Leaf leaf2 = new Leaf("规则 B");Composite composite1 = new Composite("组合规则1");composite1.add(leaf1);composite1.add(leaf2);Leaf leaf3 = new Leaf("规则 C");Composite root = new Composite("根规则");root.add(composite1);root.add(leaf3);root.operation(); // 统一调用}
}
4. 组合设计模式适合场景
组合设计模式(Composite Pattern)适用于 “树形结构” 的场景,它能让客户端以统一方式处理单个对象和组合对象。以下是其适合与不适合使用的场景总结:
4.1. ✅ 适合使用组合设计模式的场景
场景 | 说明 |
具有树形结构的业务模型 | 如文件系统、组织架构、菜单栏、风控规则树等。 |
需要统一处理单个对象和组合对象 | 客户端希望一致地使用所有元素(比如 |
组合对象和单个对象行为一致 | 当组合对象与叶子节点有相同的行为逻辑(如日志打印、状态传递等)。 |
需要支持递归组合对象 | 组合中可以包含其他组合(嵌套结构),比如组合规则中嵌套组合规则。 |
客户端不应依赖具体实现结构 | 只与 |
4.2. ❌ 不适合使用组合设计模式的场景
场景 | 原因 |
对象之间结构关系简单 | 如果业务不涉及树形结构,引入组合反而增加系统复杂度。 |
叶子节点与组合节点的行为差异很大 | 若行为差异明显(如操作参数、逻辑完全不同),统一接口会导致实现臃肿。 |
对性能要求极高的系统 | 递归遍历树结构可能导致性能瓶颈,不如精细控制流程。 |
对象的生命周期和依赖复杂 | 比如依赖注入、事务、缓存等特性在嵌套结构中处理较困难。 |
频繁变动的数据结构 | 若组合结构经常调整、扩展,维护成本会变高,灵活性不如策略模式或责任链模式。 |
4.3. 📝 实战建议(风控项目中的应用)
项目结构 | 是否适合组合模式 |
风控规则树(嵌套组合规则 + 原子规则) | ✅ 适合 |
单一条件判断规则(如手机号是否存在) | ❌ 不适合(可用策略/责任链) |
配置驱动型规则执行器 | ✅ 适合(组合模式 + Spring 注入) |
强依赖流程顺序的规则链 | ❌ 不适合(更适合责任链模式) |
5. 组合设计模式实战示例
组合设计模式实战示例 — 风控规则引擎
5.1. 统一接口 Component
public interface RuleComponent {boolean evaluate(RiskContext context);
}
evaluate
方法表示对风控上下文做规则判断,返回是否通过。
5.2. 叶子节点实现(单一规则)
import org.springframework.stereotype.Component;
import javax.annotation.Resource;@Component("blacklistRule")
public class BlacklistRule implements RuleComponent {@Resource(name = "blacklistService")private BlacklistService blacklistService; // 依赖注入,模拟黑名单校验服务@Overridepublic boolean evaluate(RiskContext context) {System.out.println("执行黑名单规则");return !blacklistService.isBlacklisted(context.getUserId());}
}
5.3. 组合节点实现(组合规则)
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;@Component("compositeRule")
public class CompositeRule implements RuleComponent {@Autowiredprivate List<RuleComponent> children; // 注入所有 RuleComponent 实现类(包括叶子和组合)@Overridepublic boolean evaluate(RiskContext context) {System.out.println("执行组合规则,包含 " + children.size() + " 条子规则");for (RuleComponent rule : children) {if (!rule.evaluate(context)) {return false; // 只要有一条规则不通过,整个组合规则失败}}return true;}
}
注意: 这里 children
注入的是所有 RuleComponent
实现,实际场景中可能用 qualifier 或配置区分组合里的具体规则。
5.4. 风控上下文类
public class RiskContext {private String userId;private double loanAmount;// 其他业务相关参数// 省略构造、getter、setterpublic RiskContext(String userId, double loanAmount) {this.userId = userId;this.loanAmount = loanAmount;}public String getUserId() { return userId; }public double getLoanAmount() { return loanAmount; }
}
5.5. 依赖服务示例(模拟黑名单服务)
import org.springframework.stereotype.Service;@Service("blacklistService")
public class BlacklistService {public boolean isBlacklisted(String userId) {// 模拟黑名单查询,假设 userId 为 "1001" 是黑名单return "1001".equals(userId);}
}
5.6. 客户端调用(风控引擎)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class RiskEngine {@Autowiredprivate RuleComponent compositeRule; // 注入组合规则,入口public boolean evaluate(RiskContext context) {return compositeRule.evaluate(context);}
}
5.7. SpringBoot 启动类及测试
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.beans.factory.annotation.Autowired;@SpringBootApplication
public class RiskApp implements CommandLineRunner {@Autowiredprivate RiskEngine riskEngine;public static void main(String[] args) {SpringApplication.run(RiskApp.class, args);}@Overridepublic void run(String... args) {RiskContext context1 = new RiskContext("1000", 5000);System.out.println("用户1000风控结果: " + riskEngine.evaluate(context1));RiskContext context2 = new RiskContext("1001", 5000);System.out.println("用户1001风控结果: " + riskEngine.evaluate(context2));}
}
总结
- 统一接口
RuleComponent
,定义规则通用方法。 - 叶子节点实现单个规则(如黑名单),组合节点实现多个规则的组合逻辑。
- 通过
@Autowired
注入,Spring 自动管理组件实例。 - 业务调用时直接使用组合规则,自动递归调用子规则。
- 采用字段注入方式,符合你“不用构造函数注入”的要求。
6. 组合设计模式思考
博文参考
- 开放平台
- 组合设计模式