如何用抽象类定义统一的 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<string bool></string>数组 +
Aggregate模拟责任链,看起来简洁,但实际问题不少:
硬伤包括: - 无法在中途修改请求内容(委托签名固定) - 调试困难:堆栈里全是
Aggregate内部帧,看不出哪个 Handler 出了问题 - 无法动态插入/跳过某个 Handler(比如根据配置开关日志 Handler) - 每次调用都重新遍历整个数组,无短路优化,性能不如原生引用跳转
委托适合简单过滤场景(如中间件管道),但真正需要“职责分离 + 动态协作 + 可调试”的业务逻辑链,还是老老实实用类继承更可控。
责任链不是越“链”越好,关键是每个
Handler的
CanHandle判断逻辑是否足够轻量,以及链条长度是否在可预期范围内——超过 5–6 层时,就得考虑是不是职责划分过细,或者该用策略模式+路由表替代了。
