职责链模式:让请求沿着链条传递

职责链模式,说白了就是给请求找一条处理流水线。每个节点只关心自己能不能处理,处理不了就往后传。我在好几个项目中都用过这个模式,最典型的就是审批流程和日志系统。

模式动机与定义

先说说为什么需要这个模式。你想想看,如果一个请求需要多个对象来处理,而且处理顺序还不固定,怎么办?

最直接的做法就是写一堆if-else:

if (handlerA.canHandle(request)) {
    handlerA.handle(request);
} else if (handlerB.canHandle(request)) {
    handlerB.handle(request);
} else if (handlerC.canHandle(request)) {
    handlerC.handle(request);
} else {
    // 没人能处理
}

嗯,这样写的问题很明显。每次新增一个处理器,就得改这段代码。而且处理顺序写死了,想调整顺序还得改代码。我在一个旧项目里就见过这样的代码,一个if-else链长达30多层,维护起来简直噩梦。

职责链模式的定义:避免请求发送者与接收者耦合,让多个对象都有机会处理请求。将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止。

核心思想:每个处理器只关心自己能不能处理,处理不了就交给下一个。这样新增处理器只需要加一个节点,不需要改现有代码。

角色与结构

职责链模式涉及三个角色:

  • Handler(抽象处理器):定义处理请求的接口,并持有下一个处理器的引用
  • ConcreteHandler(具体处理器):实现处理逻辑,如果自己能处理就处理,否则传给下一个
  • Client(客户端):构建职责链,并向链头发送请求

结构其实很简单,就是一个单向链表。每个节点都知道下一个节点是谁。

职责链模式结构图 Client(客户端) 发送请求 Handler(抽象处理器) ConcreteHandlerA 处理A类请求 ConcreteHandlerB 处理B类请求 ConcreteHandlerC 处理C类请求 next next 请求沿着链条传递,直到有处理器能处理 请求传递路径

C++代码实现

先写抽象处理器基类。这里有个小技巧:用智能指针管理next,避免内存泄漏。

#include <iostream>
#include <memory>
#include <string>

// 抽象处理器
class Handler {
protected:
    std::shared_ptr<Handler> nextHandler;
public:
    virtual ~Handler() = default;

    void setNext(std::shared_ptr<Handler> next) {
        nextHandler = next;
    }

    virtual void handle(const std::string& request) {
        if (nextHandler) {
            nextHandler->handle(request);
        } else {
            std::cout << "没人能处理这个请求: " << request << std::endl;
        }
    }
};

具体处理器实现。每个处理器只关心自己能处理的请求类型:

// 具体处理器A - 处理"TypeA"请求
class ConcreteHandlerA : public Handler {
public:
    void handle(const std::string& request) override {
        if (request == "TypeA") {
            std::cout << "ConcreteHandlerA 处理了请求: " << request << std::endl;
        } else {
            std::cout << "ConcreteHandlerA 无法处理,传给下一个" << std::endl;
            Handler::handle(request);  // 调用基类的handle,自动传递
        }
    }
};

// 具体处理器B - 处理"TypeB"请求
class ConcreteHandlerB : public Handler {
public:
    void handle(const std::string& request) override {
        if (request == "TypeB") {
            std::cout << "ConcreteHandlerB 处理了请求: " << request << std::endl;
        } else {
            std::cout << "ConcreteHandlerB 无法处理,传给下一个" << std::endl;
            Handler::handle(request);
        }
    }
};

// 具体处理器C - 处理"TypeC"请求
class ConcreteHandlerC : public Handler {
public:
    void handle(const std::string& request) override {
        if (request == "TypeC") {
            std::cout << "ConcreteHandlerC 处理了请求: " << request << std::endl;
        } else {
            std::cout << "ConcreteHandlerC 无法处理,传给下一个" << std::endl;
            Handler::handle(request);
        }
    }
};

客户端代码,构建职责链并发送请求:

int main() {
    // 构建职责链
    auto handlerA = std::make_shared<ConcreteHandlerA>();
    auto handlerB = std::make_shared<ConcreteHandlerB>();
    auto handlerC = std::make_shared<ConcreteHandlerC>();

    handlerA->setNext(handlerB);
    handlerB->setNext(handlerC);

    // 发送各种请求
    handlerA->handle("TypeA");  // 被A处理
    handlerA->handle("TypeB");  // 被B处理
    handlerA->handle("TypeC");  // 被C处理
    handlerA->handle("TypeD");  // 没人能处理

    return 0;
}

个人经验:我建议在基类Handler中提供一个默认的handle实现,这样子类只需要关注自己的处理逻辑,不需要重复写传递代码。这个习惯帮我减少了不少重复代码。

审批流程示例

职责链模式最经典的应用就是审批流程。我在一个OA系统里就用过这个模式,处理请假审批、报销审批等场景。

先定义审批请求的数据结构:

// 审批请求
struct ApprovalRequest {
    std::string applicant;   // 申请人
    double amount;           // 金额
    int days;                // 天数
    std::string reason;      // 原因
};

抽象审批处理器:

// 抽象审批处理器
class Approver {
protected:
    std::shared_ptr<Approver> nextApprover;
    std::string name;
public:
    Approver(const std::string& n) : name(n) {}
    virtual ~Approver() = default;

    void setNext(std::shared_ptr<Approver> next) {
        nextApprover = next;
    }

    virtual void approve(const ApprovalRequest& request) {
        if (nextApprover) {
            nextApprover->approve(request);
        } else {
            std::cout << "审批被拒绝: 金额" << request.amount 
                      << "超出所有审批权限" << std::endl;
        }
    }
};

具体审批角色:

// 项目经理 - 审批5000元以下
class ProjectManager : public Approver {
public:
    ProjectManager() : Approver("项目经理") {}

    void approve(const ApprovalRequest& request) override {
        if (request.amount <= 5000) {
            std::cout << name << " 审批通过: " << request.applicant 
                      << " 申请 " << request.amount << "元" << std::endl;
        } else {
            std::cout << name << " 权限不足,转交上级" << std::endl;
            Approver::approve(request);
        }
    }
};

// 部门经理 - 审批20000元以下
class DepartmentManager : public Approver {
public:
    DepartmentManager() : Approver("部门经理") {}

    void approve(const ApprovalRequest& request) override {
        if (request.amount <= 20000) {
            std::cout << name << " 审批通过: " << request.applicant 
                      << " 申请 " << request.amount << "元" << std::endl;
        } else {
            std::cout << name << " 权限不足,转交上级" << std::endl;
            Approver::approve(request);
        }
    }
};

// 总经理 - 审批100000元以下
class GeneralManager : public Approver {
public:
    GeneralManager() : Approver("总经理") {}

    void approve(const ApprovalRequest& request) override {
        if (request.amount <= 100000) {
            std::cout << name << " 审批通过: " << request.applicant 
                      << " 申请 " << request.amount << "元" << std::endl;
        } else {
            std::cout << name << " 权限不足,转交上级" << std::endl;
            Approver::approve(request);
        }
    }
};

客户端使用:

int main() {
    // 构建审批链
    auto pm = std::make_shared<ProjectManager>();
    auto dm = std::make_shared<DepartmentManager>();
    auto gm = std::make_shared<GeneralManager>();

    pm->setNext(dm);
    dm->setNext(gm);

    // 测试不同金额的审批
    ApprovalRequest req1 = {"张三", 3000, 3, "团建活动"};
    ApprovalRequest req2 = {"李四", 15000, 5, "设备采购"};
    ApprovalRequest req3 = {"王五", 50000, 10, "项目外包"};
    ApprovalRequest req4 = {"赵六", 200000, 30, "系统升级"};

    pm->approve(req1);
    std::cout << "---" << std::endl;
    pm->approve(req2);
    std::cout << "---" << std::endl;
    pm->approve(req3);
    std::cout << "---" << std::endl;
    pm->approve(req4);

    return 0;
}

输出结果:

项目经理 审批通过: 张三 申请 3000元
---
项目经理 权限不足,转交上级
部门经理 审批通过: 李四 申请 15000元
---
项目经理 权限不足,转交上级
部门经理 权限不足,转交上级
总经理 审批通过: 王五 申请 50000元
---
项目经理 权限不足,转交上级
部门经理 权限不足,转交上级
总经理 权限不足,转交上级
审批被拒绝: 金额200000超出所有审批权限

避坑指南:我曾经在一个项目中遇到职责链死循环的问题。原因是某个处理器处理完请求后,又调用了链头的handle方法,导致请求在链中循环传递。解决办法是:每个处理器要么处理请求并返回,要么只调用一次next->handle(),不要同时做两件事。

职责链模式的优缺点

优点 缺点
降低耦合度:发送者和接收者解耦 请求可能不被处理:如果链尾也没人处理
灵活性高:可以动态调整链的顺序 调试困难:请求在链中传递,难以追踪
符合开闭原则:新增处理器不需要改现有代码 性能问题:链太长会影响处理速度
每个处理器职责单一 可能形成循环链导致死循环

我的建议:使用职责链模式时,最好在基类中加一个最大链长限制,或者加一个请求ID来防止循环。另外,如果链中大部分请求都会被第一个处理器处理,那其实没必要用职责链,直接调用第一个处理器就行。职责链适合处理「不确定谁会处理」的场景。

职责链模式说白了就是「能者多劳,不能者让贤」。每个节点只做自己擅长的事,做不了就往后传。这种思想在很多场景都适用,比如日志级别过滤、事件冒泡、中间件架构等。你想想看,其实很多框架的拦截器机制,底层就是职责链模式。

在实际项目中,我建议把职责链的构建逻辑单独抽出来,不要散落在客户端代码里。可以写一个ChainBuilder或者ChainFactory,专门负责组装链。这样哪天想调整处理顺序,只需要改一个地方。


公众号:蓝海资料掘金营,微信deep3321