订单审核先查风险、再查金额上限。被风险规则拒绝的订单不应该被后面的金额规则重新批准。旧代码遍历所有处理器,把每一次决定写入同一个 last,最后的批准覆盖了前面的拒绝。问题不是处理器数量,而是“何时必须停止传递”没有合同。

三种结果决定链路去向

labs/32 的 Before.review(blocked=true, amount=500) 先得到 DENIED,最后返回 APPROVED;断言保留这个错误和 fraud,limit 两步轨迹。After 约定处理器只可能返回 NEXT、DENIED 或 APPROVED;一旦得到明确终态立刻返回,链尾全是 NEXT 时返回 UNHANDLED。因此同样的受阻订单只跑 fraud,后续金额规则不会意外翻案。正常 500 分仍批准;1001 分与空链都未处理,不被默认为通过。

1
2
3
4
5
for (Reviewer reviewer : reviewers) {
Decision decision = reviewer.review(request);
if (decision != NEXT) return decision;
}
return UNHANDLED;

Alternative 直接按风险与金额写两层 if,当规则就这两条且固定顺序时更易读。责任链适合可增加多个候选处理者、由其中一位给出终态的需求;与全执行校验流水线不同,成功或拒绝后不再无条件传给下一位。与 31 的 Observer 不同,订阅事件常需让多名观察者都看到消息;与 35 的 Mediator 不同,协调者可能需要收集所有参与者的反馈再决定。光有 List<Reviewer> 不会自动得出这些语义,关键是 NEXT 和终止条件。

方案 风险拒绝 / 500 分 没人处理 1001 分
Before 被后续批准覆盖 保留最后的 NEXT,含义不明确
After DENIED,金额规则不执行 UNHANDLED
Alternative 显式 if 返回 DENIED UNHANDLED

本例没有数据库审核、真实风控、审核人员人工复核或异步等待;数组顺序是教学选择,不是通用业务规范。

flowchart LR
    Caller[请求方] --> Chain[After 审核链]
    Chain --> Reviewer[Reviewer.review]
    Reviewer --> Decision{Decision}
    Decision -->|NEXT| Reviewer
    Decision -->|APPROVED 或 DENIED| Stop[停止返回]
    Decision -->|全链 NEXT| Missing[UNHANDLED]

验证与练习

在 examples/design-patterns/ 执行 ./mvnw -B -ntp -pl labs/32 -am test 和累计 ./mvnw -B -ntp verify,实际输入、环境、输出与退出码见 examples/design-patterns/evidence/32/RUN.md。

  1. 新增“人工复核 1001–3000 分”的处理器,先让风险拒绝、500 分批准、空链未处理的旧合同继续通过,再加新金额边界与执行轨迹断言。
  2. 如果需求变成“所有规则都必须运行以收集拒绝理由”,删除短路链写普通流水线,并通过测试说明为什么不能还把 APPROVED 当链式终态。

参考资料

上一节:31 谁管理订阅和失败;下一节:33 对象状态如何约束操作。