摘要
组合设计模式是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构,使客户端对单个对象和组合对象具有一致的访问方式。它包含抽象组件、叶子节点和组合节点,具有统一处理、支持递归结构和易于扩展等优点,适用于树形结构场景,如组织架构、菜单、规则集等。
1. 组合设计模式定义
组合设计模式(Composite Pattern)是一种结构型设计模式,它用于将对象组合成树形结构以表示“部分-整体”的层次结构,使得客户端对单个对象和组合对象具有一致的访问方式。组合模式允许你将对象组合成树形结构来表示“整体/部分”层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
1.1.1. ✅ UML 结构图(简化):
Component(抽象组件)
↑
┌──────┴──────┐
Leaf(叶子节点) Composite(容器/组合节点)
- Component:定义组合中对象的接口,可以是抽象类或接口。
- Leaf:叶子节点,表示树结构中的子节点,不能再包含其他子对象。
- Composite:组合节点,表示可以拥有子节点的对象,它实现了 Component 接口,并维护子组件集合。
1.1.2. ✅ 举例说明(风控场景类比):
比如在风控系统中,有一套规则系统:
- 单一规则(如“手机号黑名单检查”)是
Leaf
; - 组合规则(如“黑名单检查 + 地域异常组合规则”)是
Composite
; - 客户端调用统一的
evaluate()
方法即可,不需要关心规则是单一还是组合。
1.1.3. ✅ 核心优势:
优点 | 说明 |
---|---|
统一处理单个和组合对象 | 客户端代码一致,不用区别对待 |
支持递归结构 | 适用于树形结构,如组织架构、菜单、规则集 |
易于扩展 | 增加新的 Leaf 或 Composite 不影响原有代码 |
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;
}
@Override
public 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);
}
@Override
public 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. ✅ 适合使用组合设计模式的场景
场景 | 说明 |
---|---|
具有树形结构的业务模型 | 如文件系统、组织架构、菜单栏、风控规则树等。 |
需要统一处理单个对象和组合对象 | 客户端希望一致地使用所有元素(比如 evaluate() 、operation() ),而不关心它们是叶子还是组合。 |
组合对象和单个对象行为一致 | 当组合对象与叶子节点有相同的行为逻辑(如日志打印、状态传递等)。 |
需要支持递归组合对象 | 组合中可以包含其他组合(嵌套结构),比如组合规则中嵌套组合规则。 |
客户端不应依赖具体实现结构 | 只与 Component 接口交互,提高可扩展性与解耦能力。 |
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; // 依赖注入,模拟黑名单校验服务
@Override
public 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 {
@Autowired
private List<RuleComponent> children; // 注入所有 RuleComponent 实现类(包括叶子和组合)
@Override
public 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、setter
public 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 {
@Autowired
private 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 {
@Autowired
private RiskEngine riskEngine;
public static void main(String[] args) {
SpringApplication.run(RiskApp.class, args);
}
@Override
public 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 自动管理组件实例。 - 业务调用时直接使用组合规则,自动递归调用子规则。
- 采用字段注入方式,符合你“不用构造函数注入”的要求。