精读C++20设计模式——行动型设计模式:责任链

前言

​ 责任链将会是我们的第一个行动型设计模式。我需要强调的是——这一蔟设计模式实际上谈不上有更加集合的设计意图,更多的像是补足程序设计除了创建和静态架构本身,剩下的动态运行交互的部分。责任链就隶属于此。

什么是责任链

​ 责任链是这样的一种追责模式:,我们将待处理的对象投射到一个由处理句柄Processor组成的链条,每一个链条可以处理转发进入的对象,可以拦截对象,可以坐视不管对象,总之,走完这个处理流程,我们就处理了对象。

​ 经典的两种责任链的形式,包含指针链和代理链两种,我们都来仔细品味一下。

指针链(Pointer Chain)

​ 在最基础的实现里,责任链往往是指针串联的。也就是说,每一个处理器(Handler)手里都保存着一个 next 指针,指向链条上的下一个节点。当请求到来时,当前节点先处理(或者决定不处理),然后再把请求交给下一个节点,直到责任链的末尾。

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

class Handler {
public:
    virtual ~Handler() = default;
    void set_next(std::shared_ptr<Handler> next) { next_ = next; }

    void handle(const std::string& req) {
        if (!process(req) && next_) {
            next_->handle(req);
        }
    }

protected:
    virtual bool process(const std::string& req) = 0;

private:
    std::shared_ptr<Handler> next_;
};

class AuthHandler : public Handler {
protected:
    bool process(const std::string& req) override {
        if (req == "auth") {
            std::cout << "AuthHandler handled\n";
            return true;
        }
        return false;
    }
};

class LogHandler : public Handler {
protected:
    bool process(const std::string& req) override {
        if (req == "log") {
            std::cout << "LogHandler handled\n";
            return true;
        }
        return false;
    }
};

int main() {
    auto auth = std::make_shared<AuthHandler>();
    auto log = std::make_shared<LogHandler>();
    auth->set_next(log);

    auth->handle("log");   // LogHandler handled
    auth->handle("auth");  // AuthHandler handled
    auth->handle("xxx");   // nobody handles
}

​ 当然,咱们如果不想要按照链表的方式,采用直接映射或者是其他办法,都是可以随意变通的。咱们随时都可以处理和替换上面的链表。但是就需要咱们自己维护。还是有点风险


代理链(Proxy Chain)

​ 另一个常见实现是代理链。它的核心思想是:请求的处理不是节点自己决定“要不要传递”,而是由链式代理类统一调度。每一个节点都被包装成代理,由代理控制“是否调用下一个”。这种方式广泛用于 拦截器(Interceptor)中间件(Middleware) 等框架中。

#include <iostream>
#include <vector>
#include <functional>

class ProxyChain {
public:
    using Handler = std::function<void(ProxyChain&)>;

    void add(Handler h) {
        handlers_.push_back(std::move(h));
    }

    void proceed() {
        if (index_ < handlers_.size()) {
            auto h = handlers_[index_++];
            h(*this);
        }
    }

private:
    std::vector<Handler> handlers_;
    size_t index_ = 0;
};

int main() {
    ProxyChain chain;

    chain.add([](ProxyChain& next){
        std::cout << "Auth check\n";
        next.proceed();
    });

    chain.add([](ProxyChain& next){
        std::cout << "Logging\n";
        next.proceed();
    });

    chain.add([](ProxyChain&){
        std::cout << "Final handler\n";
    });

    chain.proceed();
}

输出:

Auth check
Logging
Final handler

​ 代理链显然要比经典的指针链更加的解耦合,我们来让一个完全的代理框架作为控制的一个中心,节点自己只会去负责自己如何实现问题。而且这样的话,咱们不用关心交底动作的实现,可以请求控制中心自己做这个事情。

异步责任链(Async Chain)

​ 在今天,随着CPU核心的快速增加,显然异步是一个非常值得考虑的要素。这里笔者扩展一下异步责任链,实际上没有什么新鲜的。我们只是和 Promisefuture 的概念结合。我们可以改造一下:

#include <iostream>
#include <future>
#include <vector>
#include <functional>

class AsyncChain {
public:
    using Handler = std::function<std::future<void>()>;

    void add(Handler h) { handlers_.push_back(std::move(h)); }

    void run() {
        std::future<void> fut = std::async(std::launch::async, [this]{
            for (auto& h : handlers_) {
                h().get(); // 等待每一步完成
            }
        });
        fut.get();
    }

private:
    std::vector<Handler> handlers_;
};

int main() {
    AsyncChain chain;
    chain.add([]{
        return std::async([]{ std::cout << "Step 1 async\n"; });
    });
    chain.add([]{
        return std::async([]{ std::cout << "Step 2 async\n"; });
    });
    chain.run();
}

​ 可以看到,我们的每一个AsyncChain还是那种“链式的分发”(当然可以改成单链表穿起来的形式,这个随意,如果我们的起点关心整体,那咱们就使用std::vector或者是std::list统计起来),但是显然我们不直接等结果,而是等待每一个的异步结果。

其他常见设计变种

环形链(Circular Chain)

环形链的思想很直白:责任链的尾部重新连回链头,形成一个闭环。这样,当一个请求流转到末尾时,它并不会自然结束,而是会继续回到第一个处理器。这个特性使得它天然适合需要循环处理的场景,比如调度器或事件广播。

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

class Handler {
public:
    Handler(std::string name): name_(std::move(name)) {}
    void set_next(std::shared_ptr<Handler> next) { next_ = next; }

    void handle(const std::string& req, int round) {
        std::cout << name_ << " processing " << req << " (round " << round << ")\n";
        if (round > 0 && next_) {
            next_->handle(req, round - 1);
        }
    }
private:
    std::string name_;
    std::shared_ptr<Handler> next_;
};

int main() {
    auto a = std::make_shared<Handler>("A");
    auto b = std::make_shared<Handler>("B");
    auto c = std::make_shared<Handler>("C");

    a->set_next(b);
    b->set_next(c);
    c->set_next(a); // 环形

    a->handle("task", 5);
}

这里的代码中,链条是 A -> B -> C -> A 的循环,每个节点都会处理任务,并且我们人为设置了 round 来中止循环。环形责任链的迭代意义在于,它从单向链表的形式演化为循环结构,可以持续“传球”直到任务完成或条件满足。


树形链(Tree Chain)

责任链不一定是直线型的,它也可以扩展成一棵树。在 GUI 系统里,事件常常会从根窗口逐层传递到子控件,又可能“冒泡”返回上层,这就是树形责任链的一个典型例子。

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

class Node {
public:
    Node(std::string name): name_(std::move(name)) {}
    void add_child(std::shared_ptr<Node> child) { children_.push_back(child); }

    void handle(const std::string& req) {
        std::cout << name_ << " received " << req << "\n";
        for (auto& child : children_) {
            child->handle(req);
        }
    }
private:
    std::string name_;
    std::vector<std::shared_ptr<Node>> children_;
};

int main() {
    auto root = std::make_shared<Node>("Root");
    auto panel = std::make_shared<Node>("Panel");
    auto button = std::make_shared<Node>("Button");
    auto textbox = std::make_shared<Node>("Textbox");

    root->add_child(panel);
    panel->add_child(button);
    panel->add_child(textbox);

    root->handle("click");
}

在这个例子中,请求像水一样“流淌”到所有子节点。代码演化的意义是:我们从链表结构走向树形结构,责任链的扩展性更强,但同时控制流也更复杂。


策略链(Chain + Strategy)

如果我们希望责任链节点更加灵活,可以在运行时决定使用哪种处理逻辑,就会自然引入策略模式。每个节点内部并不绑定具体的处理逻辑,而是持有一个可变的策略。

#include <iostream>
#include <functional>
#include <memory>

class Handler {
public:
    Handler(std::function<bool(const std::string&)> strat): strategy_(std::move(strat)) {}
    void set_next(std::shared_ptr<Handler> next) { next_ = next; }

    void handle(const std::string& req) {
        if (!strategy_(req) && next_) {
            next_->handle(req);
        }
    }
private:
    std::function<bool(const std::string&)> strategy_;
    std::shared_ptr<Handler> next_;
};

int main() {
    auto auth = std::make_shared<Handler>([](const std::string& req){
        if (req == "auth") { std::cout << "auth ok\n"; return true; }
        return false;
    });
    auto log = std::make_shared<Handler>([](const std::string& req){
        if (req == "log") { std::cout << "logged\n"; return true; }
        return false;
    });

    auth->set_next(log);
    auth->handle("log");
}

这种演化让责任链更加动态化,策略函数相当于“插拔式”的逻辑。迭代意义是我们不再需要为每个节点单独写一个类,而是将处理逻辑解耦出来,形成可组合的责任链。

可回溯责任链(Reversible Chain)

最后一个变种是支持回退的责任链。当某个节点处理失败时,可以沿着链路回退到前面节点执行补偿操作。这在事务系统里非常常见。

#include <iostream>
#include <vector>
#include <functional>

class ReversibleChain {
public:
    using Action = std::function<bool()>;	// 正向
    using Undo = std::function<void()>;		// 反向

    void add(Action act, Undo undo) {
        steps_.push_back({std::move(act), std::move(undo)});
    }

    void run() {
        size_t executed = 0;
        for (; executed < steps_.size(); ++executed) {
            if (!steps_[executed].first()) break;
        }
        if (executed < steps_.size()) {
            std::cout << "rollback...\n";
            for (int i = executed - 1; i >= 0; --i) {
                steps_[i].second();
            }
        }
    }

private:
    struct Step {
        Action first;
        Undo second;
    };
    std::vector<Step> steps_;
};

int main() {
    ReversibleChain chain;
    chain.add([]{ std::cout << "step1 ok\n"; return true; },
              []{ std::cout << "undo1\n"; });
    chain.add([]{ std::cout << "step2 fail\n"; return false; },
              []{ std::cout << "undo2\n"; });
    chain.run();
}

这段代码模拟了事务处理:前两个步骤执行时,第二步失败触发回退机制。它的演化意义是责任链不仅支持“正向传递”,也支持“反向回溯”,增强了鲁棒性。

总结

责任链处理什么问题?

责任链要解决的核心问题是:当请求需要被多个处理者依次尝试时,我们不想在调用方显式地写死“谁来处理”。如果调用方必须了解所有的处理逻辑并逐个调用,那么系统会高度耦合且不利于扩展

责任链如何解决上面的问题?

责任链的解决方式很优雅:把处理逻辑抽象为节点,并把节点通过链式结构组织起来。调用方只需要把请求交给链的起点,链会自动沿着节点传递下去,直到某个节点完成处理或到达终点为止。这样,调用方和具体处理者就彻底解耦,链条的组合方式也可以在运行时自由变化。


各种变种的优劣对比

变种优点缺点典型做法/应用场景
指针链实现直观、轻量、灵活替换节点链路长时不易追踪,组织逻辑要手动维护经典的 Handler->set_next() 模式
代理链请求调度交给框架统一管理,节点只专注业务逻辑节点失去部分“是否传递”的自主性,依赖代理机制中间件框架、拦截器、过滤器
环形链天然适合循环调度和轮询,能保证请求持续传递必须设置中止条件,否则可能陷入无限循环调度器、轮询器、事件广播
树形链支持多路分发,能表达复杂的层次化逻辑(如 GUI 事件)结构复杂,请求流向和调试难度更高GUI 事件冒泡、日志多目的地
策略链节点逻辑可动态替换,支持插拔式策略组合层次感增加,逻辑分布在策略函数中,阅读成本提升规则引擎、权限校验
异步责任链自然支持异步流程,避免阻塞,适配现代异步框架控制流复杂,需要处理异常、超时和上下文传递Web 中间件、IO 任务链
可回溯责任链支持回退/补偿操作,适合事务场景,鲁棒性高实现复杂,需要保存上下文和反向操作数据库事务、分布式 Saga、撤销/重做