大家好,我是十三!欢迎来到十三Tech。
去年做一个报销审批系统——金额 ≤1000 元组长批,≤10000 元经理批,≤100000 元总监批,更大金额走 CFO。最初的代码是一堆 if amount <= 1000 { ... } else if amount <= 10000 { ... },每加一个审批层级就要改一遍审批入口。
后来抽了一个责任链:LeaderHandler → ManagerHandler → DirectorHandler → CFOHandler,每个节点能批就批、批不了转给 next。审批入口变成 chain.Handle(req) 一行,新增层级只要插一个 handler。
很多人把责任链和中间件混为一谈——代码长得几乎一样。这一篇就聊聊责任链的标志特征是"链上节点自己决定是否处理",以及它跟装饰器、策略的边界。
一、责任链在解决什么——不是"链式调用"
教科书定义:将请求沿处理者链传递,每个处理者决定自己处理还是传给下一位。这话模糊——任何链式调用都可以套。真正的责任链有一个标志特征——链上节点自己决定是否处理。
责任链有两种典型变体——拒绝型链和管道型链。审批流是拒绝型:某个节点处理了就结束,不传给 next;HTTP 中间件是管道型:所有节点都执行,但任何节点都可以提前拒绝(401、429)。
很多人把这俩混为一谈。Go 社区习惯把"管道型链"叫中间件(middleware),把"拒绝型链"叫责任链(chain of responsibility)。两者代码结构一样,但语义不同——责任链是"谁能处理谁处理",中间件是"大家都过一遍"。
判断口诀:链上最多一个节点处理 → 责任链;所有节点都处理 → 装饰器/中间件;客户端主动选一个 → 策略。
二、责任链在 Go 里怎么写——BaseHandler + SetNext
责任链的核心写法是每个 Handler 持有下一个 Handler 的引用,能处理就处理、不能处理就转给 next。
type Handler interface {
SetNext(h Handler) Handler
Handle(req *Request) error
}
type BaseHandler struct{ next Handler }
func (b *BaseHandler) SetNext(h Handler) Handler { b.next = h; return h }
func (b *BaseHandler) Handle(req *Request) error {
if b.next != nil { return b.next.Handle(req) }
return ErrUnhandled
}
type LeaderHandler struct {
BaseHandler
maxAmount int
}
func (h *LeaderHandler) Handle(req *Request) error {
if req.Amount <= h.maxAmount { return h.approve(req) }
return h.next.Handle(req)
}
BaseHandler 把 next 引用和链式组装封装好,具体 handler 嵌入它,只需要实现自己的 Handle——能处理就处理、不能处理就 h.next.Handle(req)。链式组装:leader.SetNext(manager).SetNext(director),SetNext 返回 next 让链式调用成为可能。
写责任链有个工程坑:链要有兜底。最后一级如果还是不能处理,要返回明确的错误("无人能审批此金额"),不能静默通过。静默通过意味着请求"消失"了——业务上很难排查。
三、什么时候别用责任链
责任链不是所有"多步骤"场景都适用,至少有三个反模式。
反模式一:链上节点强依赖顺序。如果"必须先 A 后 B 才能调 C"——这是固定的算法骨架,应该用模板方法。责任链假设节点独立,谁先谁后无所谓,强依赖说明选错模式了。
反模式二:所有节点都执行。如果你写出的链每个节点都调 next.Handle——那这是装饰器/中间件,不是责任链。责任链的核心是"拒绝"——某个节点处理完就结束,不再往下传。
反模式三:链超过 10 层。一个 panic 堆栈十几层 Handle,定位真正出问题的节点要花十分钟。这种深度通常说明审批/处理流程设计有问题,该考虑用工作流引擎(如 Cadence、Temporal)。
Go 标准库里责任链最经典的地方是 net/http 的 middleware 链——func(http.Handler) http.Handler 这种签名让多个中间件能链式组装。虽然语义上是装饰器+责任链的混合(所有节点都执行 + 可中途拒绝),但代码骨架就是教科书级的责任链。看 gorilla/mux、chi、gin 这些框架的 Use/UseMiddleware 方法,全是这套结构。
回头看那个报销审批——值得用责任链吗?值得,多级审批、金额阈值清晰、新增层级频繁,正是责任链的甜区。但如果只是固定两级的简单审批(组长→经理),两个 if 反而更直接。
责任链真正教会我的,不是"请求沿链传递",而是让"谁能处理"由节点自己决定——调用方不关心最终是谁处理的,只关心请求有结果。这个解耦比模式本身重要得多。
下一篇讲观察者,它解决的是"一个对象变化通知多个订阅者"的问题。
关于十三Tech 资深服务端研发,AI 实践者,专注分享真实可落地的技术经验。 相信 AI 是程序员的最佳搭档。
联系方式:569893882@qq.com GitHub:@TriTechAI
