应使用抽象类Handler而非接口,因其可提供SetNext和默认委托调用逻辑,避免重复代码与空引用风险;关键在于持有_next引用、Handle返回bool控制链路、SetNext支持链式调用。

如何用抽象类定义统一的 Handler 接口
责任链的核心是让每个处理器能处理请求,也能把请求传给下一个。C# 中最稳妥的方式是定义一个抽象基类 Handler,而不是接口——因为接口无法提供默认的 SetNext 和委托调用逻辑,容易导致重复代码或空指针风险。
关键点在于:
- Handler 必须持有对下一个 Handler 的引用(可为空)
- 处理方法(如 Handle)应返回 bool 或 object 表示是否终结链条,避免隐式跳过后续节点
- SetNext 应返回 this,支持链式调用(如 h1.SetNext(h2).SetNext(h3))
public abstract class Handler
{
protected Handler? _next;
public Handler SetNext(Handler next) { _next = next; return this; }
public virtual bool Handle(string request)
{
return _next?.Handle(request) == true;
}
}
具体 Handler 如何判断是否处理并决定是否继续
每个子类重写 Handle 时,必须显式判断当前请求是否属于自己的职责范围;若不处理,不能直接 return false,而应调用 _next?.Handle(...),否则链条会意外中断。
常见错误:
- 在条件不满足时写 return false;,导致后续 Handler 完全没机会执行
- 忘记检查 _next 是否为 null,引发 NullReferenceException
- 把业务逻辑和“是否继续”耦合太紧,比如用 if (x) { ... } else { return _next?.Handle(...) },但漏掉对 _next 的空值保护
推荐写法:
- 用 if (CanHandle(request)) { ... return true; } 明确职责边界
- 结尾统一用 return _next?.Handle(request) == true;,既安全又语义清晰
- 若需透传结果(如修改后的 request),可改用 string? Handle(string request),返回 null 表示终止
如何组装链条并避免循环引用或遗漏
手动串联时最容易出错的是顺序颠倒、漏设 SetNext,或无意中形成环(比如 a.SetNext(b); b.SetNext(a);)。生产环境建议用工厂或配置驱动构建。
实操建议:
- 链条起点必须是非 null 的第一个 Handler,且终点 Handler 的 _next 必须为 null
- 单元测试里加断言:遍历链条长度 ≤ 预期数,且无重复实例(可用 Object.ReferenceEquals 检查)
- 如果 Handler 有状态(如计数器、缓存),注意多线程下是否线程安全;无状态 Handler 可复用,有状态的建议每次新建
- 不要用静态字段保存链条,会导致不同请求互相干扰
为什么不用委托链或 LINQ Aggregate 实现
有人尝试用 Func 数组 + Aggregate 模拟责任链,看起来简洁,但实际问题不少:
硬伤包括:
- 无法在中途修改请求内容(委托签名固定)
- 调试困难:堆栈里全是 Aggregate 内部帧,看不出哪个 Handler 出了问题
- 无法动态插入/跳过某个 Handler(比如根据配置开关日志 Handler)
- 每次调用都重新遍历整个数组,无短路优化,性能不如原生引用跳转
委托适合简单过滤场景(如中间件管道),但真正需要“职责分离 + 动态协作 + 可调试”的业务逻辑链,还是老老实实用类继承更可控。
责任链不是越“链”越好,关键是每个 Handler 的 CanHandle 判断逻辑是否足够轻量,以及链条长度是否在可预期范围内——超过 5–6 层时,就得考虑是不是职责划分过细,或者该用策略模式+路由表替代了。








