责任链模式在C#中需手动构建请求流水线,Handler必须返回bool以明确终结或流转,DI注册须保序,审批流应使用ApprovalResult替代bool,并分离横切关注点,不支持回溯场景需用Saga。

责任链模式在 C# 中不是靠“自动装配”或“框架魔法”工作的,它本质是一条由你手动控制走向的请求流水线;用错抽象方式、忽略终止逻辑、或在 DI 中乱序注册,三者任一都会让链在运行时静默失效。
Handler 抽象类必须返回 bool,且基类统一处理 next 调用
别用 void Handle(Request) —— 无法表达“我处理完了”还是“我不管,你接着传”。返回 bool 是最轻量、最可控的信号:
-
true表示已终结,后续节点不应执行 -
false表示不处理,交由_next?.Handle()继续流转 - 基类必须封装
_next?.Handle(request)调用,子类只专注判断条件,不写重复的空值检查 - 漏掉
return _next?.Handle(request) == true;是高频错误:条件不满足时直接return false;,等于把请求丢进黑洞
DI 注册必须保序,GetServices<IHandler>() 不保证顺序
.NET 的 GetServices<IHandler>() 返回的是无序集合,哪怕你按顺序 AddTransient,运行时顺序也可能颠倒。生产环境必须显式保序:
- 用
ChainBuilder类在ConfigureServices中链式构建:services.AddSingleton<IHandler>(sp => new AuthHandler()).AddSingleton<IHandler>(sp => new ValidationHandler()),再按注册顺序取 - 避免在
Handle()内部通过sp.GetService<IHandler>()动态解析——会绕过生命周期管理,还可能触发循环依赖 - 若 Handler 有状态(如缓存上下文),务必标记为
Scoped或每次新建;复用单例 + 状态 = 并发脏读
审批流等业务链必须带元数据与中断语义,不能只靠 true/false
真实审批场景中,“拒绝”和“超时”不是终点,而是需要路由、补偿、升级的事件。用裸 bool 会丢失关键信息:
- 返回
ApprovalResult类型,含IsApproved: bool、Reason: string、NextStep?: string、Metadata: Dictionary<string, object> - 超时必须由调度器统一检查:
ApprovalContext.StartedAt+CancellationToken注入远程调用,而非每个 Handler 自己计时 - 拒绝不等于终止:可返回
ApprovalResult.Reject("BUDGET_EXCEEDED")触发前端跳转补材料,而非简单false - 同一
ContextId重复进链时,应查缓存直接返回历史ApprovalResult,否则幂等性崩塌
拦截器和责任链必须分离,Attribute + AOP 才是正解
权限校验、参数验证、审计日志这些横切关注点,如果硬塞进每个 IApprovalStep.Handle(),会导致三重污染:
- 职责混杂:一个类既做业务决策又做权限检查,单元测试难写、逻辑难复用
- 扩展僵化:加个新日志字段,得改所有 Handler;而用
[Audit]Attribute,改一处全局生效 - 跨服务失能:Web API 的拦截器无法复用于 gRPC 或后台任务,但 AOP 切面可统一注入
- 正确姿势:Handler 只管“是否批准”,用
IAuthorizationHandler、IValidator<T>、IAuditLogger等独立接口 +Attribute标记,由中间件或代理统一织入
最易被忽略的一点:责任链不是万能胶,它天然不支持回溯。A 处理完传给 B,B 失败了,A 已退出栈帧——你没法让 A 撤销自己刚写的数据库记录。需要补偿逻辑的地方,别强行套链,该用 Saga 就用 Saga。


















