责任链模式(Chain of Responsibility)
一、模式定义
责任链模式将请求沿着一条处理者链传递。每个处理者决定是自己处理这个请求,还是传给链上的下一个处理者。发送者不需要知道哪个处理者最终会处理请求。
一句话理解:像公司审批流程——你的请假单先到组长,组长能批就批,批不了就转给经理,经理批不了再转给总监。 申请人不需要知道最终是谁批的,只要知道单子递上去了。
二、解决什么问题
- 发送者与接收者解耦:发送者不需要知道具体由谁处理,只管丢给链头
- 动态调整处理顺序:运行时可以增删处理者或调整链的顺序
- 单一职责:每个处理者只关心自己能处理的请求类型,不关心其他
- 消除条件分支:不需要用 if-else 判断该走哪个处理器,由链自动路由
三、核心角色
| 角色 | 职责 |
|---|---|
| Handler(处理者接口) | 声明处理方法,并持有下一个处理者的引用 |
| ConcreteHandler | 实现具体处理逻辑,能处理就处理,不能就传给下一个 |
| Client(客户端) | 构造责任链,将请求发送给链头 |
四、结构图
┌──────────────────────────────────────────────────────────────┐
│ Client ──-> Handler A ──-> Handler B ──-> Handler C ──-> nil │
│ 能处理? 能处理? 能处理? │
│ ├─是─> 处理 ├─是─> 处理 ├─是─> 处理 │
│ └─否─> 传递 └─否─> 传递 └─否─> 丢弃 │
└──────────────────────────────────────────────────────────────┘
五、Go 实现要点
Go 实现责任链有两种风格:
1. 经典链表风格
每个处理者持有 next Handler 字段,在构造时手动串联。处理方法接收请求,能处理就返回结果,不能就调用 next.Handle()。这是最传统的实现方式。
2. 函数切片风格(Go 推荐)
用 []func(req) error 切片存储处理函数,遍历切片直到某个函数返回 ErrHandled 终止。这种方式更 Go-idiomatic,不需要定义处理者结构体。
两种风格各有优势:链表风格适合处理者需要维护状态的场景;切片风格适合无状态函数式处理。
六、完整练习代码
以 HTTP 请求中间件链 为场景:请求依次经过日志记录 → 鉴权 → 限流 → 业务处理。
package main
import (
"fmt"
"strings"
)
// ============================================================
// 请求与响应结构
// ============================================================
// Request 模拟 HTTP 请求
type Request struct {
Path string
Method string
Token string // 认证 Token
Headers map[string]string // 请求头
Body string // 请求体
}
// Response 模拟 HTTP 响应
type Response struct {
StatusCode int
Body string
Headers map[string]string
}
func NewResponse() *Response {
return &Response{
Headers: make(map[string]string),
}
}
// ============================================================
// 处理者接口(经典链表风格)
// ============================================================
// Handler 处理者接口
type Handler interface {
// Handle 处理请求
// 返回 (handled bool, resp *Response)
// handled=true 表示已处理,不再传递
// handled=false 表示不处理,继续传递给下一个
Handle(req *Request) (bool, *Response)
// SetNext 设置下一个处理者
SetNext(h Handler) Handler
// Name 处理者名称
Name() string
}
// BaseHandler 提供默认的链管理实现
// 具体处理者嵌入 BaseHandler 即可获得 next 字段管理能力
type BaseHandler struct {
next Handler
}
func (b *BaseHandler) SetNext(h Handler) Handler {
b.next = h
return h // 返回 h 支持链式调用:a.SetNext(b).SetNext(c)
}
func (b *BaseHandler) Next(req *Request) (bool, *Response) {
if b.next != nil {
return b.next.Handle(req)
}
// 链上没有处理者能处理此请求
return false, nil
}
// ============================================================
// 具体处理者实现
// ============================================================
// --- 1. 日志中间件 ---
type LogHandler struct {
BaseHandler
}
func (h *LogHandler) Name() string { return "日志" }
func (h *LogHandler) Handle(req *Request) (bool, *Response) {
fmt.Printf("[日志] %s %s token=%s\n", req.Method, req.Path, req.Token)
// 日志不拦截请求,只记录后继续传递
return h.Next(req)
}
// --- 2. 鉴权中间件 ---
type AuthHandler struct {
BaseHandler
validTokens map[string]bool // 有效的 token 集合
}
func NewAuthHandler(tokens []string) *AuthHandler {
m := make(map[string]bool)
for _, t := range tokens {
m[t] = true
}
return &AuthHandler{validTokens: m}
}
func (h *AuthHandler) Name() string { return "鉴权" }
func (h *AuthHandler) Handle(req *Request) (bool, *Response) {
// 公开路径不需要鉴权
if strings.HasPrefix(req.Path, "/public") {
fmt.Printf("[鉴权] 公开路径 %s,跳过鉴权\n", req.Path)
return h.Next(req)
}
// 检查 Token
if !h.validTokens[req.Token] {
resp := NewResponse()
resp.StatusCode = 401
resp.Body = "Unauthorized: invalid token"
fmt.Printf("[鉴权] 拒绝访问: token 无效\n")
return true, resp // handled=true,拦截请求
}
fmt.Printf("[鉴权] 通过鉴权\n")
return h.Next(req)
}
// --- 3. 限流中间件 ---
type RateLimitHandler struct {
BaseHandler
maxRequests int // 最大请求数
counter map[string]int // 按 IP 计数
}
func NewRateLimitHandler(max int) *RateLimitHandler {
return &RateLimitHandler{
maxRequests: max,
counter: make(map[string]int),
}
}
func (h *RateLimitHandler) Name() string { return "限流" }
func (h *RateLimitHandler) Handle(req *Request) (bool, *Response) {
// 模拟按 token 限流
key := req.Token
if key == "" {
key = "anonymous"
}
h.counter[key]++
if h.counter[key] > h.maxRequests {
resp := NewResponse()
resp.StatusCode = 429
resp.Body = "Too Many Requests"
fmt.Printf("[限流] 请求 %s 超限: %d/%d\n", key, h.counter[key], h.maxRequests)
return true, resp // 拦截
}
fmt.Printf("[限流] 请求 %s 计数: %d/%d\n", key, h.counter[key], h.maxRequests)
return h.Next(req)
}
// --- 4. 业务处理 ---
type BusinessHandler struct {
BaseHandler
}
func (h *BusinessHandler) Name() string { return "业务" }
func (h *BusinessHandler) Handle(req *Request) (bool, *Response) {
resp := NewResponse()
switch {
case req.Path == "/api/hello":
resp.StatusCode = 200
resp.Body = `{"message": "Hello, World!"}`
fmt.Printf("[业务] 处理 %s -> 200\n", req.Path)
case req.Path == "/api/user":
resp.StatusCode = 200
resp.Body = `{"id": 1, "name": "张三"}`
fmt.Printf("[业务] 处理 %s -> 200\n", req.Path)
default:
resp.StatusCode = 404
resp.Body = "Not Found"
fmt.Printf("[业务] 未知路径 %s -> 404\n", req.Path)
}
return true, resp // 业务处理总是返回 handled=true
}
// ============================================================
// 辅助:构建责任链
// ============================================================
// BuildChain 构建处理链,返回链头
// 用法: chain := BuildChain(log, auth, rateLimit, business)
func BuildChain(handlers ...Handler) Handler {
if len(handlers) == 0 {
return nil
}
for i := 0; i < len(handlers)-1; i++ {
handlers[i].SetNext(handlers[i+1])
}
return handlers[0]
}
// ============================================================
// 主函数
// ============================================================
func main() {
// 构建责任链:日志 -> 鉴权 -> 限流 -> 业务
logH := &LogHandler{}
authH := NewAuthHandler([]string{"valid-token-123", "admin-token-456"})
rateH := NewRateLimitHandler(3) // 每个 token 最多 3 次请求
bizH := &BusinessHandler{}
chain := BuildChain(logH, authH, rateH, bizH)
fmt.Println("===== 1. 正常请求(有效Token)=====")
req1 := &Request{
Path: "/api/hello",
Method: "GET",
Token: "valid-token-123",
}
_, resp1 := chain.Handle(req1)
fmt.Printf("响应: %d %s\n\n", resp1.StatusCode, resp1.Body)
fmt.Println("===== 2. 鉴权失败(无效Token)=====")
req2 := &Request{
Path: "/api/user",
Method: "GET",
Token: "invalid-token",
}
_, resp2 := chain.Handle(req2)
fmt.Printf("响应: %d %s\n\n", resp2.StatusCode, resp2.Body)
fmt.Println("===== 3. 公开路径(无需鉴权)=====")
req3 := &Request{
Path: "/public/health",
Method: "GET",
Token: "",
}
_, resp3 := chain.Handle(req3)
fmt.Printf("响应: %d %s\n\n", resp3.StatusCode, resp3.Body)
fmt.Println("===== 4. 限流测试(连续请求超限)=====")
for i := 1; i <= 5; i++ {
fmt.Printf("--- 第 %d 次请求 ---\n", i)
req := &Request{
Path: "/api/hello",
Method: "GET",
Token: "admin-token-456",
}
_, resp := chain.Handle(req)
fmt.Printf("响应: %d %s\n", resp.StatusCode, resp.Body)
}
}
七、运行结果
===== 1. 正常请求(有效Token)=====
[日志] GET /api/hello token=valid-token-123
[鉴权] 通过鉴权
[限流] 请求 valid-token-123 计数: 1/3
[业务] 处理 /api/hello -> 200
响应: 200 {"message": "Hello, World!"}
===== 2. 鉴权失败(无效Token)=====
[日志] GET /api/user token=invalid-token
[鉴权] 拒绝访问: token 无效
响应: 401 Unauthorized: invalid token
===== 3. 公开路径(无需鉴权)=====
[日志] GET /public/health token=
[鉴权] 公开路径 /public/health,跳过鉴权
[限流] 请求 anonymous 计数: 1/3
[业务] 未知路径 /public/health -> 404
响应: 404 Not Found
===== 4. 限流测试(连续请求超限)=====
--- 第 1 次请求 ---
[日志] GET /api/hello token=admin-token-456
[鉴权] 通过鉴权
[限流] 请求 admin-token-456 计数: 1/3
[业务] 处理 /api/hello -> 200
响应: 200 {"message": "Hello, World!"}
--- 第 2 次请求 ---
[日志] GET /api/hello token=admin-token-456
[鉴权] 通过鉴权
[限流] 请求 admin-token-456 计数: 2/3
[业务] 处理 /api/hello -> 200
响应: 200 {"message": "Hello, World!"}
--- 第 3 次请求 ---
[日志] GET /api/hello token=admin-token-456
[鉴权] 通过鉴权
[限流] 请求 admin-token-456 计数: 3/3
[业务] 处理 /api/hello -> 200
响应: 200 {"message": "Hello, World!"}
--- 第 4 次请求 ---
[日志] GET /api/hello token=admin-token-456
[鉴权] 通过鉴权
[限流] 请求 admin-token-456 超限: 4/3
响应: 429 Too Many Requests
--- 第 5 次请求 ---
[日志] GET /api/hello token=admin-token-456
[鉴权] 通过鉴权
[限流] 请求 admin-token-456 超限: 5/3
响应: 429 Too Many Requests
八、关键设计决策解析
1. BaseHandler 嵌入式复用
Go 没有继承,但可以通过结构体嵌入实现代码复用。BaseHandler 提供了 next 字段和 SetNext/Next 方法。具体处理者嵌入 BaseHandler 后,只需实现 Handle 和 Name 两个方法即可,不用重复写链管理代码。
2. "处理后是否传递"的两种语义
- 拦截式(本实现鉴权/限流):能处理就拦截,不传给下一个。用
handled=true表示请求已被处理 - 透传式(本实现日志):只做记录不拦截,永远
handled=false继续传递
这两种语义可以混用:日志处理者总是透传,鉴权和限流在条件满足时拦截。
3. BuildChain 链式构造
BuildChain(handlers...) 函数接收可变参数,自动串联链。使用时只需传入处理者列表,不需要手动一个个 SetNext。这种 API 设计让责任链的构造代码非常简洁:
chain := BuildChain(log, auth, rate, biz)
4. 函数式责任链替代方案
当处理者不需要状态时,可以用函数切片替代结构体链:
type Middleware func(req *Request) (*Response, bool)
func ProcessChain(chain []Middleware, req *Request) *Response {
for _, mw := range chain {
resp, handled := mw(req)
if handled {
return resp
}
}
return nil
}
Go 的 http.Handler 中间件就是这个思路——func(http.Handler) http.Handler 签名的函数层层包装。
九、责任链 vs 管道模式
两者都涉及"请求沿链传递",但有细微区别:
| 维度 | 责任链模式 | 管道模式 |
|---|---|---|
| 处理方式 | 某个处理者处理后终止 | 每个处理者都参与处理 |
| 请求是否变化 | 请求不变,只做路由 | 请求可能被逐步加工 |
| 终止条件 | 处理者决定 | 通常不终止 |
| 类比 | 信访转办 | 工厂流水线 |
Go HTTP 中间件是管道模式的变体——每个中间件都处理请求(加 header、记日志),然后调用 next.ServeHTTP 继续。但中间件也可以选择不调用 next 来提前终止(如鉴权失败返回 401),所以实际是两种模式的混合体。
十、实际项目身影
- Go
net/http中间件:func(http.Handler) http.Handler,典型的函数式责任链 - Gin/Echo 框架:中间件链,支持
c.Next()继续和c.Abort()终止 - 审批系统:请假单按金额路由到不同级别审批人
- 异常处理:Go 中
defer/recover本质上也是责任链——defer 链中第一个 recover 捕获 panic 后终止传播 - Kubernetes 准入控制器:请求经过 Authentication → Authorization → Mutating Admission → Schema Validation → Validating Admission 链
十一、小结
责任链模式的核心价值是让请求发送者不需要知道具体接收者。在 Go 中,借助结构体嵌入和接口,可以非常优雅地实现链表式责任链。当处理者无状态时,函数式切片风格更简洁。实际项目中最常见的应用就是 HTTP 中间件——理解了责任链模式,就理解了所有 Go Web 框架中间件机制的本质。
&spm=1001.2101.3001.5002&articleId=163231457&d=1&t=3&u=4acec68b18a94bea9443d5810d610ff6)
119

被折叠的 条评论
为什么被折叠?



