规则执行器设计与实现:优化复杂条件判断
1. 规则执行器的设计背景与核心思路
最近在开发一个用户试用申请系统的规则扩展功能时,遇到了一个典型的条件判断难题。原有的系统通过大量if-else语句来实现各种条件判断,随着业务规则越来越复杂,代码变得难以维护。比如要判断用户是否符合试用条件,需要检查:
- 用户所在地区是否在服务范围内
- 用户账号是否完成实名认证
- 用户历史订单是否超过限制
- 用户信用评分是否达标
这些条件之间有的是"与"关系(所有条件都要满足),有的是"或"关系(满足任一即可)。更麻烦的是,有些条件的判断成本很高(比如调用外部信用评分接口),如果前面的条件已经不满足,后续的判断其实没必要执行。
1.1 传统if-else方案的痛点
在原有系统中,代码大概是这样的结构:
if(regionService.check(user.getRegion())) { if(user.isVerified()) { if(orderService.checkOrderCount(user.getId()) || creditService.checkScore(user.getId())) { // 通过申请 } } }这种写法有几个明显问题:
- 嵌套层级太深,可读性差
- 新增或修改规则需要改动核心逻辑
- 无法实现短路求值(比如region检查不通过时,仍然会执行后续检查)
- 单元测试困难,各种条件组合需要大量测试用例
1.2 规则执行器的设计思路
规则执行器的核心思想是将业务规则抽象为独立的对象,通过执行器来统一管理和执行这些规则。具体来说:
- 每个业务规则实现统一的接口
- 执行器负责按顺序执行规则
- 支持AND/OR等逻辑关系
- 支持短路求值(某个规则失败后跳过后续不必要检查)
- 规则之间通过上下文共享数据
这样设计后,业务规则的变更就只需要新增或修改规则类,不会影响核心执行逻辑。
2. 规则执行器的具体实现
2.1 规则接口设计
首先定义一个基础的规则接口:
public interface Rule<T> { /** * 执行规则检查 * @param context 执行上下文 * @return 检查结果 */ boolean evaluate(T context); /** * 规则名称(用于日志和监控) */ String getName(); /** * 是否应该继续执行后续规则(用于短路逻辑) */ default boolean shouldContinueOnFailure() { return false; } }2.2 具体规则实现
然后针对每个业务条件实现具体的规则类。例如地区检查规则:
public class RegionRule implements Rule<ApplyContext> { @Override public boolean evaluate(ApplyContext context) { return regionService.check(context.getUser().getRegion()); } @Override public String getName() { return "RegionRule"; } }信用评分规则:
public class CreditScoreRule implements Rule<ApplyContext> { @Override public boolean evaluate(ApplyContext context) { // 如果已经检查过信用评分,直接使用缓存结果 if(context.getCreditScore() != null) { return context.getCreditScore() > 600; } // 否则调用信用服务获取评分 int score = creditService.getScore(context.getUser().getId()); context.setCreditScore(score); // 缓存结果供其他规则使用 return score > 600; } @Override public String getName() { return "CreditScoreRule"; } }2.3 执行器实现
执行器负责管理规则并控制执行流程:
public class RuleEngine<T> { private final List<Rule<T>> rules = new ArrayList<>(); public void addRule(Rule<T> rule) { rules.add(rule); } public boolean execute(T context) { for (Rule<T> rule : rules) { boolean result = rule.evaluate(context); if (!result && !rule.shouldContinueOnFailure()) { return false; // 短路返回 } } return true; } }对于OR关系的规则组,可以这样实现:
public class OrRuleGroup<T> implements Rule<T> { private final List<Rule<T>> rules = new ArrayList<>(); public void addRule(Rule<T> rule) { rules.add(rule); } @Override public boolean evaluate(T context) { for (Rule<T> rule : rules) { if (rule.evaluate(context)) { return true; // 任一规则通过即可 } } return false; } }2.4 上下文设计
上下文对象用于在规则间共享数据:
public class ApplyContext { private User user; private Integer creditScore; private Integer orderCount; // 其他共享字段... // getters and setters }3. 规则执行器的使用示例
3.1 构建规则引擎
RuleEngine<ApplyContext> engine = new RuleEngine<>(); // AND关系的规则 engine.addRule(new RegionRule()); engine.addRule(new VerificationRule()); // OR关系的规则组 OrRuleGroup<ApplyContext> orGroup = new OrRuleGroup<>(); orGroup.addRule(new OrderCountRule()); orGroup.addRule(new CreditScoreRule()); engine.addRule(orGroup);3.2 执行规则检查
ApplyContext context = new ApplyContext(); context.setUser(currentUser); boolean passed = engine.execute(context); if (passed) { // 通过申请 } else { // 拒绝申请 }4. 规则执行器的进阶优化
4.1 性能优化技巧
- 规则排序:把失败概率高的简单规则放在前面,可以尽早短路
- 结果缓存:对于耗时的规则检查(如信用评分),结果可以缓存在上下文中
- 并行执行:对于没有依赖关系的规则可以并行执行
// 并行执行示例 public boolean parallelExecute(T context) { return rules.parallelStream() .allMatch(rule -> rule.evaluate(context) || rule.shouldContinueOnFailure()); }4.2 监控与调试
- 记录每个规则的执行结果和耗时
- 提供规则执行轨迹,方便排查问题
- 支持动态调整规则顺序或开关
public boolean executeWithMonitor(T context) { for (Rule<T> rule : rules) { long start = System.currentTimeMillis(); boolean result = rule.evaluate(context); long cost = System.currentTimeMillis() - start; monitor.record(rule.getName(), result, cost); if (!result && !rule.shouldContinueOnFailure()) { return false; } } return true; }4.3 规则配置化
可以将规则配置存储在数据库或配置文件中,实现动态加载:
{ "rules": [ { "name": "RegionRule", "className": "com.example.rules.RegionRule", "continueOnFailure": false }, { "name": "CreditOrOrderRule", "type": "OR", "rules": [ { "className": "com.example.rules.OrderCountRule" }, { "className": "com.example.rules.CreditScoreRule" } ] } ] }5. 规则执行器的适用场景与局限性
5.1 适用场景
- 复杂的业务规则判断
- 需要频繁变更的业务逻辑
- 多条件组合的判断场景
- 需要详细执行记录的业务流程
5.2 局限性
- 对于简单的条件判断,可能会过度设计
- 规则之间的数据依赖需要小心处理
- 调试复杂度比直接写if-else高
5.3 替代方案比较
- 策略模式:适合行为可替换的场景
- 责任链模式:适合流程处理的场景
- 状态机:适合有明确状态转移的场景
- DSL规则引擎:如Drools,适合非常复杂的业务规则
在实际项目中,我通常会先使用简单的规则执行器模式,当规则复杂度达到一定程度后再考虑引入完整的规则引擎。过早优化和过度设计都会带来维护成本。
6. 实际项目中的经验教训
- 上下文设计要谨慎:避免在上下文中存放过多数据,导致规则间隐式耦合
- 规则粒度要适中:太细的规则会导致数量爆炸,太粗的规则失去灵活性
- 注意线程安全:如果规则引擎被多线程共享,需要确保规则实现是线程安全的
- 版本兼容性:当规则接口变更时,要考虑已有规则的兼容处理
一个常见的错误是在规则中直接修改上下文中的共享对象,这会导致难以追踪的副作用。更好的做法是让规则返回需要更新的数据,由执行器统一应用变更。
// 不推荐的做法 public class BadRule implements Rule<ApplyContext> { public boolean evaluate(ApplyContext context) { context.setSomeField(computeValue()); // 直接修改上下文 return true; } } // 推荐的做法 public class GoodRule implements Rule<ApplyContext> { public boolean evaluate(ApplyContext context) { return computeValue(); // 返回计算结果 } } // 在执行器中处理结果 if (rule.evaluate(context)) { context.setSomeField(((GoodRule)rule).getComputedValue()); }规则执行器模式特别适合业务规则复杂的系统,如风控系统、促销系统、工作流系统等。通过将业务规则从主流程中解耦出来,可以大大提高系统的可维护性和扩展性。
