Golang - 责任链模式(Chain of Responsibility)

责任链模式(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 后,只需实现 HandleName 两个方法即可,不用重复写链管理代码。

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 框架中间件机制的本质。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值