C++11 简化 “责任链模式” 的一种思路

334 阅读3分钟

「这是我参与11月更文挑战的第 12 天,活动详情查看:2021最后一次更文挑战」。

参加该活动的第 27 篇文章

正文

责任链模式:使多个对象都有机会处理外部请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理了请求为止。

举一个简单的实际场景 —— 假设员工要求加薪。公司的管理者一共有三级,总经理、总监、经理,如果一个员工要求加薪,应该向主管的经理申请,如果加薪的数量在经理的职权内,那么经理可以直接批准,否则将申请上交给总监。总监的处理方式也一样,总经理可以处理所有请求。

这就是典型的责任链模式,请求的处理形成了一条链,直到有一个对象处理请求为止。。

责任链模式结构图

image.png

  • 责任链的特点是:当客户提交一个请求时,请求是沿链传递直至有一个对象负责处理它(或者所有对象都无法处理)。
  • 责任链的好处是:请求者不用管哪个对象来处理。每个处理对象只需保持一个后继者(next)即可。
  • 要注意的是:一个请求到链的最后可能也没有处理,所以一定要配置得当。

举例

以实现一个传统的责任链模式为例,它具有一个请求类和一个抽象处理对象类、三个具体处理对象类

/// @note 请求类
struct Request
{
    int RequestType;
};

/// @note 抽象处理对象类
class Handler
{
protected:
    std::shared_ptr<Handler> m_next; ///< 后继者

public:
    /// @note 配置后继者
    Handler(std::shared_ptr<Handler> next) : m_next(next) {}
    /// @not 派生类实现具体的处理
    virtual void HandleRequest(Request) = 0;
};

class ConcreteHandler1 : public Handler
{
public:
    ConcreteHandler1(std::shared_ptr<Handler> next) : Handler(next) {}

    void HandleRequest(Request request)
    {
        if (request.RequestType == 1) ///< 负责处理请求 1
        {
            cout << "request handled in ConcreteHandler1" << endl;
        }
        else
        {
            if (m_next != nullptr) ///< 否则交给后继者
                m_next->HandleRequest(request);
        }
    }
};

class ConcreteHandler2 : public Handler
{
public:
    ConcreteHandler2(std::shared_ptr<Handler> next) : Handler(next) {}

    void HandleRequest(Request request)
    {
        if (request.RequestType == 2) ///< 负责处理请求 2
        {
            cout << "request handled in ConcreteHandler2" << endl;
        }
        else
        {
            if (m_next != nullptr) ///< 否则交给后继者
                m_next->HandleRequest(request);
        }
    }
};

class ConcreteHandler3 : public Handler
{
public:
    ConcreteHandler3(std::shared_ptr<Handler> next) : Handler(next) {}

    void HandleRequest(Request request)
    {
        if (request.RequestType == 3) ///< 负责处理请求 3
        {
            cout << "request handled in ConcreteHandler3" << endl;
        }
        else
        {
            if (m_next != nullptr) ///< 否则交给后继者
                m_next->HandleRequest(request);
        }
    }
};

如果改用 C++11 的 function 对象实现以上责任链模式的代码的话,则代码可以大为精简,具体如下所示

class ChainHandler
{

public:
    std::function<void(Request)> function;
    
    void HandleRequest(Request request)
    {
        function(request); ///< 开始将请求传递给 function 链条
    }

    std::function<void(Request)> &getfunction()
    {
        return function;
    }
};

/// @note 构造 function 链条
void assemble(std::function<void(Request)> call, 
              std::function<void(Request)> next, 
              Request request)
{
    if (next != nullptr)
        next(request); 
    else
        call(request); 
}

具体调用为


/// @note 构造具体的处理对象,并串联起来(设置后继者)
auto thirdHandler = std::make_shared<ConcreteHandler3>(nullptr);
auto secondHandler = std::make_shared<ConcreteHandler2>(thirdHandler);
auto firstHandler = std::make_shared<ConcreteHandler1>(secondHandler);

Request request = {2};

ChainHandler chain;

/// @note 准备好各个具体处理对象的函数对象
std::function<void(Request)> f1 = std::bind(&ConcreteHandler1::HandleRequest, firstHandler, std::placeholders::_1);
std::function<void(Request)> f2 = std::bind(&ConcreteHandler2::HandleRequest, secondHandler, std::placeholders::_1);
std::function<void(Request)> f3 = std::bind(&ConcreteHandler3::HandleRequest, thirdHandler, std::placeholders::_1);

/// @note 通过 assemble 不断地往 function 链条中加 function,最后调用的时候会从链条的第一个 function 开始调用。
chain.function = std::bind(&assemble, f1, chain.function, std::placeholders::_1);
chain.function = std::bind(&assemble, f2, chain.function, std::placeholders::_1);
chain.function = std::bind(&assemble, f3, chain.function, std::placeholders::_1);

/// @note function 链条开始处理请求
chain.HandleRequest(request);

总结

使用函数对象取代虚函数的一个理由是它去除了继承的限制,实现了松耦合,方法实现更加灵活,然而这个优点也可能成了它的缺点,当需要替代的虚函数增多时,组装 function 的复杂度也在增加,太松散了导致代码不够直观,代码的内聚性也变低了。

所以在此只是提供一个思路,具体问题还需要根据实际应用场景进行斟酌