搜 索

🚀 责任链模式 (Chain of Responsibility)

  • 1阅读
  • 2023年10月22日
  • 0评论
首页 / 编程 / 正文

前言:那些年我们写过的"屎山 if-else"

相信每一个后端程序员都有过这样的经历:

public void processRequest(Request request) {
    if (!checkLogin(request)) {
        throw new UnauthorizedException("没登录,回家洗洗睡");
    }
    if (!checkPermission(request)) {
        throw new ForbiddenException("没权限,洗洗也睡");
    }
    if (!checkRateLimit(request)) {
        throw new TooManyRequestsException("别刷了,睡觉");
    }
    if (!checkBlacklist(request)) {
        throw new BlockedException("你已被拉黑,永久失眠");
    }
    // 终于到业务逻辑了……
    doActualWork(request);
}

改需求了,加一个风控校验?找到那个 processRequest,小心翼翼地在第 3 行和第 4 行之间插一段 if。

三个月后,新来的同事盯着这 200 行的方法,欲言又止,最后只说了一句:"这写的什么鬼……"

这就是责任链模式要解决的问题。


一、什么是责任链模式?

将请求的发送者与处理者解耦,让多个对象都有机会处理请求,并将这些对象连成一条链,请求沿着链传递,直到被处理为止。

用大白话说:流水线工人 模型。

每个工人(Handler)只干自己那摊事:

  • 能处理 → 处理
  • 不能处理 / 不归我管 → 丢给下一个工人

没有任何一个工人需要知道整条流水线长什么样,更不用知道你后面还有谁。


二、核心结构

classDiagram class Handler { <> -Handler nextHandler +setNext(handler: Handler) Handler +handle(request: Request) Response } class ConcreteHandlerA { +handle(request: Request) Response } class ConcreteHandlerB { +handle(request: Request) Response } class ConcreteHandlerC { +handle(request: Request) Response } class Client { +sendRequest(request: Request) } Handler <|-- ConcreteHandlerA Handler <|-- ConcreteHandlerB Handler <|-- ConcreteHandlerC Handler o--> Handler : nextHandler Client --> Handler : uses

核心角色就三个:

角色职责
Handler(抽象处理者)定义处理接口,持有下一个处理者的引用
ConcreteHandler(具体处理者)实现自己的处理逻辑,决定是否传递给下一个
Client(客户端)组装责任链,发起请求

三、请求流转图

API 网关鉴权流程 为例,一条典型的责任链长这样:

flowchart LR C([📱 客户端请求]) --> A subgraph Chain ["🔗 责任链"] A[🔑 登录校验 AuthHandler] B[🛡️ 权限校验 PermissionHandler] D[⏱️ 限流校验 RateLimitHandler] E[🚫 黑名单校验 BlacklistHandler] F[💼 业务处理 BusinessHandler] A -->|通过| B B -->|通过| D D -->|通过| E E -->|通过| F end A -->|❌ 拦截| R1([401 未登录]) B -->|❌ 拦截| R2([403 无权限]) D -->|❌ 拦截| R3([429 限流]) E -->|❌ 拦截| R4([403 已拉黑]) F --> R5([✅ 200 成功])

每个 Handler 干完自己的活,要么拦截,要么放行。业务逻辑完全不用知道前面发生过什么。


四、代码实现(Go 版本)

为什么用 Go?因为 Joey 喜欢 Go。从入门到放弃系列:Go 篇早就放弃了,但责任链还没放弃。

4.1 定义 Handler 接口

package chain

// Request 请求上下文
type Request struct {
    UserID  string
    Token   string
    IP      string
    Path    string
    Payload map[string]interface{}
}

// Response 响应
type Response struct {
    Code    int
    Message string
    Data    interface{}
}

// Handler 处理者接口
type Handler interface {
    SetNext(handler Handler) Handler
    Handle(req *Request) *Response
}

4.2 抽象基类(减少重复)

// BaseHandler 基础处理者,提供链接能力
type BaseHandler struct {
    next Handler
}

func (b *BaseHandler) SetNext(handler Handler) Handler {
    b.next = handler
    return handler // 返回 handler 支持链式调用
}

// PassToNext 传递给下一个处理者
func (b *BaseHandler) PassToNext(req *Request) *Response {
    if b.next != nil {
        return b.next.Handle(req)
    }
    // 链条末端,默认放行
    return &Response{Code: 200, Message: "OK"}
}

4.3 具体 Handler 实现

// ---- 登录校验 ----
type AuthHandler struct {
    BaseHandler
}

func (h *AuthHandler) Handle(req *Request) *Response {
    if req.Token == "" {
        return &Response{Code: 401, Message: "未登录,你是谁?"}
    }
    // 实际项目里这里验 JWT
    fmt.Println("[Auth] 登录校验通过 ✅")
    return h.PassToNext(req)
}

// ---- 权限校验 ----
type PermissionHandler struct {
    BaseHandler
    allowedPaths map[string][]string // userRole -> []path
}

func (h *PermissionHandler) Handle(req *Request) *Response {
    // 简化:通过 Token 长度判断权限(别学我,这只是 demo)
    if len(req.Token) < 10 {
        return &Response{Code: 403, Message: "权限不足,你配吗?"}
    }
    fmt.Println("[Permission] 权限校验通过 ✅")
    return h.PassToNext(req)
}

// ---- 限流校验 ----
type RateLimitHandler struct {
    BaseHandler
    counter map[string]int // IP -> 请求次数(真实项目用 Redis)
    limit   int
}

func (h *RateLimitHandler) Handle(req *Request) *Response {
    h.counter[req.IP]++
    if h.counter[req.IP] > h.limit {
        return &Response{Code: 429, Message: "太快了,缓一缓"}
    }
    fmt.Println("[RateLimit] 限流校验通过 ✅")
    return h.PassToNext(req)
}

// ---- 黑名单校验 ----
type BlacklistHandler struct {
    BaseHandler
    blacklist map[string]bool
}

func (h *BlacklistHandler) Handle(req *Request) *Response {
    if h.blacklist[req.IP] {
        return &Response{Code: 403, Message: "你已被列入黑名单,永久告别"}
    }
    fmt.Println("[Blacklist] 黑名单校验通过 ✅")
    return h.PassToNext(req)
}

// ---- 业务处理(终点) ----
type BusinessHandler struct {
    BaseHandler
}

func (h *BusinessHandler) Handle(req *Request) *Response {
    fmt.Println("[Business] 终于轮到我了,处理业务逻辑 🎉")
    return &Response{Code: 200, Message: "success", Data: "业务数据"}
}

4.4 组装责任链

func main() {
    // 1. 创建各个处理者
    auth := &AuthHandler{}
    permission := &PermissionHandler{}
    rateLimit := &RateLimitHandler{
        counter: make(map[string]int),
        limit:   100,
    }
    blacklist := &BlacklistHandler{
        blacklist: map[string]bool{"1.2.3.4": true},
    }
    business := &BusinessHandler{}

    // 2. 组装链(链式调用,优雅!)
    auth.SetNext(permission).
        SetNext(rateLimit).
        SetNext(blacklist).
        SetNext(business)

    // 3. 发起请求
    req := &Request{
        UserID: "joey",
        Token:  "eyJhbGciOiJIUzI1NiJ9.xxx",
        IP:     "10.0.0.1",
        Path:   "/api/payment",
    }

    resp := auth.Handle(req)
    fmt.Printf("Response: %d %s\n", resp.Code, resp.Message)
}

输出:

[Auth] 登录校验通过 ✅
[Permission] 权限校验通过 ✅
[RateLimit] 限流校验通过 ✅
[Blacklist] 黑名单校验通过 ✅
[Business] 终于轮到我了,处理业务逻辑 🎉
Response: 200 success

五、真实场景:多级审批工作流

这是责任链最经典的场景之一。以报销审批为例:

stateDiagram-v2 [*] --> 提交申请 提交申请 --> 直属领导审批: 金额 ≤ 1000 直属领导审批 --> 通过 : ✅ 批准 直属领导审批 --> 拒绝 : ❌ 驳回 直属领导审批 --> 部门经理审批 : 金额 > 1000 部门经理审批 --> 通过 : ✅ 批准(≤ 5000) 部门经理审批 --> 拒绝 : ❌ 驳回 部门经理审批 --> CFO审批 : 金额 > 5000 CFO审批 --> 通过 : ✅ 批准 CFO审批 --> 拒绝 : ❌ 驳回 通过 --> [*] 拒绝 --> [*]
type ApprovalRequest struct {
    Amount      float64
    Description string
    ApplicantID string
}

// TeamLeaderHandler 直属领导:审批 1000 以内
type TeamLeaderHandler struct {
    BaseHandler
}

func (h *TeamLeaderHandler) Handle(req *ApprovalRequest) string {
    if req.Amount <= 1000 {
        return fmt.Sprintf("✅ 直属领导批准:%.0f 元报销通过", req.Amount)
    }
    fmt.Printf("[TeamLeader] %.0f 元超出权限,上报部门经理\n", req.Amount)
    return h.PassToNext(req) // 超过权限,往上传
}

// ManagerHandler 部门经理:审批 5000 以内
type ManagerHandler struct {
    BaseHandler
}

func (h *ManagerHandler) Handle(req *ApprovalRequest) string {
    if req.Amount <= 5000 {
        return fmt.Sprintf("✅ 部门经理批准:%.0f 元报销通过", req.Amount)
    }
    fmt.Printf("[Manager] %.0f 元超出权限,上报 CFO\n", req.Amount)
    return h.PassToNext(req)
}

// CFOHandler CFO:无上限(CFO 也是人,也会皱眉头)
type CFOHandler struct {
    BaseHandler
}

func (h *CFOHandler) Handle(req *ApprovalRequest) string {
    if req.Amount > 50000 {
        return fmt.Sprintf("❌ CFO 拒绝:%.0f 元?你是来抢钱的吗?", req.Amount)
    }
    return fmt.Sprintf("✅ CFO 批准:%.0f 元,下不为例", req.Amount)
}

六、与 Middleware 的关系

如果你用过 Gin、Echo、Koa、Express,你天天在用责任链,只是你可能叫它 Middleware(中间件)

sequenceDiagram participant C as 客户端 participant M1 as Logger 中间件 participant M2 as Auth 中间件 participant M3 as Cors 中间件 participant H as 业务 Handler C->>M1: HTTP Request M1->>M1: 记录请求日志 M1->>M2: next() M2->>M2: 验证 Token M2->>M3: next() M3->>M3: 添加跨域头 M3->>H: next() H->>H: 执行业务逻辑 H-->>M3: Response M3-->>M2: Response M2-->>M1: Response M1->>M1: 记录响应日志 M1-->>C: HTTP Response

Gin 的中间件本质上就是责任链的一种变体,只不过它是双向的(请求向下传,响应向上回)。

// Gin 中间件 = 责任链的工程实践
r := gin.New()

r.Use(
    middleware.Logger(),      // Handler 1
    middleware.Auth(),        // Handler 2
    middleware.RateLimit(),   // Handler 3
)

r.GET("/api/payment", handler.Payment)

七、优缺点分析

✅ 优点

mindmap root((责任链优点)) 解耦 发送者不知道谁处理 处理者不知道链有多长 灵活扩展 新增步骤只需写新 Handler 不修改已有代码 符合开闭原则 单一职责 每个 Handler 只关心自己 代码清晰可维护 顺序可控 组装时决定顺序 运行时动态调整链

❌ 缺点

问题说明
调试困难请求在链中流转,出问题不好定位(加日志!)
性能开销链条过长时每个节点都要处理,注意性能
请求可能未被处理如果所有 Handler 都不处理,请求会悄悄消失
链条管理成本链条顺序错误会导致逻辑 bug,需统一管理

八、与其他模式对比

quadrantChart title 设计模式选型参考 x-axis 请求是否需要多个处理者 --> 单个处理者足够 y-axis 处理者是否需要解耦 --> 处理者紧密耦合 责任链: [0.15, 0.85] 装饰器: [0.3, 0.4] 策略模式: [0.75, 0.6] 命令模式: [0.6, 0.3] 观察者: [0.2, 0.2]
模式核心区别
责任链请求沿链传递,每个节点可拦截,处理者间解耦
装饰器层层包裹增强功能,每层都会执行,不会中断
策略模式选择一个策略执行,不传递请求
命令模式将请求封装为对象,支持撤销/重做

九、工程实践建议

在实际 支付/金融系统 中使用责任链,有几个踩过的坑要特别提一下:

9.1 务必打印链条日志

func (b *BaseHandler) PassToNext(req *Request) *Response {
    if b.next != nil {
        log.Printf("[Chain] %T → %T", b, b.next) // 打印链路
        return b.next.Handle(req)
    }
    return &Response{Code: 200, Message: "OK"}
}

9.2 链条末端要有兜底

// 末端 Handler,防止请求"消失"
type FallbackHandler struct{}

func (h *FallbackHandler) Handle(req *Request) *Response {
    log.Warnf("[Chain] 请求未被任何 Handler 处理: %+v", req)
    return &Response{Code: 500, Message: "内部错误:没有人处理你的请求"}
}

9.3 链条顺序要集中管理

// chain/factory.go —— 统一在这里组装链,不要散落各处
func BuildAPIChain() Handler {
    auth       := &AuthHandler{}
    permission := &PermissionHandler{}
    rateLimit  := &RateLimitHandler{limit: 100}
    blacklist  := &BlacklistHandler{}
    business   := &BusinessHandler{}
    fallback   := &FallbackHandler{}

    auth.SetNext(permission).
        SetNext(rateLimit).
        SetNext(blacklist).
        SetNext(business).
        SetNext(fallback)

    return auth
}

9.4 支付系统中的应用

在 NPSS/Aani 这类即时支付场景中,责任链的变体随处可见:

flowchart TD A[收到支付请求] --> B[IBAN 格式校验] B -->|✅| C[账户状态校验] C -->|✅| D[余额校验] D -->|✅| E[风控规则校验] E -->|✅| F[合规 AML 校验] F -->|✅| G[路由到支付渠道] G --> H[✅ 发起支付] B -->|❌| X1[拒绝: IBAN 格式错误] C -->|❌| X2[拒绝: 账户已冻结] D -->|❌| X3[拒绝: 余额不足] E -->|❌| X4[拒绝: 风控拦截] F -->|❌| X5[拒绝: AML 命中] style H fill:#22c55e,color:#fff style X1 fill:#ef4444,color:#fff style X2 fill:#ef4444,color:#fff style X3 fill:#ef4444,color:#fff style X4 fill:#ef4444,color:#fff style X5 fill:#ef4444,color:#fff

每个节点都可以单独测试、单独监控、单独上线。新加一个 欺诈检测 节点?在风控和 AML 之间插入即可,其他代码零修改。


十、总结

责任链模式 的精髓是:每个人只负责自己该负责的,剩下的不是我的事。

这听起来像是甩锅,但在系统设计里,这叫 单一职责,这叫 解耦,这叫 优雅

当你下次看到一个长达几百行、全是 if-else 的校验方法时,不要骂写这段代码的人——可能那个人就是三个月前的你。

把它重构成责任链吧,让未来的你感谢今天的你。

从入门到放弃系列的精神:
入门——你写 if-else
进阶——你写责任链
放弃——你写框架让别人写责任链
顿悟——你发现框架里还是 if-else

评论区
暂无评论
avatar