设计模式 32:审核何时停止传递
订单审核先查风险、再查金额上限。被风险规则拒绝的订单不应该被后面的金额规则重新批准。旧代码遍历所有处理器,把每一次决定写入同一个 last,最后的批准覆盖了前面的拒绝。问题不是处理器数量,而是“何时必须停止传递”没有合同。
三种结果决定链路去向
labs/32 的 Before.review(blocked=true, amount=500) 先得到 DENIED,最后返回 APPROVED;断言保留这个错误和 fraud,limit 两步轨迹。After 约定处理器只可能返回 NEXT、DENIED 或 APPROVED;一旦得到明确终态立刻返回,链尾全是 NEXT 时返回 UNHANDLED。因此同样的受阻订单只跑 fraud,后续金额规则不会意外翻案。正常 500 分仍批准;1001 分与空链都未处理,不被默认为通过。
1 | |
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。
- 新增“人工复核 1001–3000 分”的处理器,先让风险拒绝、500 分批准、空链未处理的旧合同继续通过,再加新金额边界与执行轨迹断言。
- 如果需求变成“所有规则都必须运行以收集拒绝理由”,删除短路链写普通流水线,并通过测试说明为什么不能还把
APPROVED当链式终态。
参考资料
- GoF 原书公开图书馆 PDF,5.1 Chain of Responsibility,目录页标注起始页 251:https://cpcc.chd.gov.in/Content/PDFs/w8UkV3tWNyEtsbUZSWJ7fVhuB9A3tsGYdm4w6VGzg2wUTNFYikqnvvFbbkiW2zmfspPEghd7QTamiMby3lVIBemrhdVWwt6rOQnm.pdf 。
- GoF Chain of Responsibility 原书摘录,Pearson/InformIT:https://www.informit.com/articles/article.aspx?p=1398601 。
- 实验代码:
examples/design-patterns/labs/32/。
上一节:31 谁管理订阅和失败;下一节:33 对象状态如何约束操作。






