首页 > 编程笔记 > C++笔记 阅读:8

C++责任链模式及其实现(非常详细)

责任链模式是一种避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者连成一条链。当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止或者链上的每个处理者都执行自己需要完成的步骤。

在责任链模式中,抽象处理者角色用于定义一个处理请求的接口,包含抽象处理方法和一个后继链接。具体处理者角色用于实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。客户类角色负责创建处理链,并向链头的具体处理者对象提交请求,但它不关心处理细节和请求的传递过程。

责任链模式的主要优点是系统可以在不影响客户端的情况下动态地重新组织链和分配责任,处理者有两种选择:承担责任或者把责任推给下家。同时,请求可以在链上进行传递,直到有对象处理它为止,从而提高了系统的灵活性和可扩展性。

责任链模式也有一些缺点,例如在纯责任链中要求每个处理者要么处理要么直接传给下一个处理者,这可能会限制系统的灵活性。此外,由于请求在链上传递时需要经过多个处理者,所以可能会导致处理效率低下。

传统责任链模式

传统责任链模式通常由 3 个组件构成,分别是处理者(Handler)、具体处理者(ConcreteHandler)和客户端(Client),如下图所示。


图 1 传统责任链模式UML简图


传统的责任链模式在请求发生后会在责任链上逐步进行处理,直到符合条件的处理者完成处理为止。这样的设计容易造成处理效率低下,并且限制了责任链模式的适用范围。

责任链模式需要多个处理者按照规定的顺序进行处理,直到最后一个处理完成。这种应用场景也是非常常见的,例如针对一份文档进行签字的工作,首先由编撰者签署,由组长审核,由经理复核,由总经理批准,在这种场景中每次操作都是在上一次操作的基础上进行处理的,存在着先后顺序,并且每个参与处理的对象都会对数据进行处理。采用传统的设计模式无法满足要求。

C++11元编程下的结构设计

本节中责任链模式 handler 使用宏进行处理,利用了可变参的宏和纯虚函数简化并规范接口定义,在业务代码中则可以针对接口实现多种不同的 concreteHandler。通过虚函数和可变宏参数实现了在编译期和运行期的多态,如下图所示。


图 2 C++11模板责任链模式UML简图

处理对象使用返回值控制处理动作是否需要继续,采用返回值控制的好处是既能够实现找到处理的对象为止,也能够使所有的处理对象按照处理顺序参与到处理过程中。

C++责任链模式实现和解析

责任链模式模块分成 3 部分实现:
使用继承定义的接口类用来在编译期检查实际的接口声明是否有正确的来源;利用宏声明定义可以方便快速且规范地定义接口。

空结构体作为基础接口类,责任链中的 handler 都应该是这个类的子类。虽然在 respItfc__ 中不做任何声明,但是能够帮助在编译期检查实际定义的接口源头是否正确。同时使用 private__ 名字空间将其约束在责任链模块中不对外暴露,这样业务模块就需要使用宏模块进行处理,以提高代码的安全性,代码如下:
#include<list>
//designM/rspsLink.hpp

namespace private__
{
    struct respItfc__{};
}

下面是 3 个用来声明接口的宏函数。使用 3 个宏可以保证接口继承关闭,方便程序在编译期可以通过检查。使用方法定义宏能够保证接口名字和责任链模块保持一致,同时又提供可变参数的功能,能够在保证接口名字不变的情况下,接口参数可以灵活使用。接口方法采用了纯虚函数的方式进行声明,这要求在使用模块时必须在自己的代码中实现已经声明的接口方法,否则是无法通过编译的。

宏 DECLARE_RESPLINK_ITFC 用来声明接口,实际上是定义一个名字作为给定参数name的结构体,这个接口体从 private__::respItfc__ 继承以保证在编译期对接口继承关系的检查能够通过,代码如下:
#define DECLARE_RESPLINK_ITFC(name)  \
    struct name:public private__::respItfc__{
宏函数 RESPLINK_ITFC_MTD(name,...) 则用来声明接口函数,函数的名字是通过 name 参数指明的。函数的参数类型表则通过可以变参的宏来处理。

END_DECLARE_RESPLINK_ITFC() 则用来关闭接口类的定义。

DECLARE_RESPLINK_ITFC_DEFAULT 宏用来声明一个默认的接口类,接口类的名字是 name。接口方法的名字是 operation,参数类型表则通过可变参的宏提供,代码如下:
//designM/rspsLink.hpp

#define RESPLINK_ITFC_MTD(name,...) virtual bool name( __VA_ARGS__) = 0;

#define END_DECLARE_RESPLINK_ITFC() };

#define DECLARE_RESPLINK_ITFC_DEFAULT(name,...)  \
DECLARE_RESPLINK_ITFC(name)            \
RESPLINK_ITFC_MTD(operation,__VA_ARGS__)      \
END_DECLARE_RESPLINK_ITFC()

模板参数 ITFC_TYPE 对应于 handler,是使用上面的宏定义的接口,责任链上所有的对象都应该从这种类型继承并实现对应的接口。使用 std::is_base_of<> 元函数对接口类型的来源关系进行检查,保证接口关系正确和接口函数有效,代码如下:
//designM/rspsLink.hpp

template< typename ITFC_TYPE >
class rspsLink
{
public:
    using itfc_t = typename std::remove_pointer<
                   typename std::decay< ITFC_TYPE >::type >::type;
    //检查继承关系,如果检查失败,则模块无法使用
    static_assert(
        std::is_base_of< private__::respItfc__, itfc_t >::value,"");
    //利用std::list记录所有的handler,这个模块本身并不对对象的生命周期进行控制
    using link_t = std::list< private__::respItfc__ * >;
    using iterator = typename link_t::iterator;
protected:
    link_t      m_link__;
public:
    rspsLink(){}
    virtual~rspsLink(){}

request() 模板函数针对所有的 handler 进行处理,函数支持可变函数参数。这样就和可变参数的宏一致了。当 operation() 函数的返回值为 false 时继续处理循环,放弃链表上的后续处理者的处理机会。将实际判断是否需要连续处理的逻辑延迟到业务实现时,有效地提高了责任链模式的通用性,代码如下:
//designM/rspsLink.hpp

template< typename...Params >
void request(Params&&...params)
{
    for(auto item:m_link__){
        auto * p = item;
        if(p == nullptr)continue;
        //调用接口函数,如果返回值是false,则结束操作
        bool rst = p->operation(std::forward<Params >(params)...);
        if(rst == false){break;}
    }
}

模板函数 requestCallback() 提供了回调函数的操作方式,可以将每个 handler 暴露给回调函数进行处理。在这种情况下,接口函数可以延迟到回调函数进行选择处理,在业务代码中可以更加灵活地控制接口函数,使处理者对象可以使用多个处理算法对数据进行处理。这样在定义接口时可以不声明任何接口函数,也可以声明多个不同的接口函数。

回调函数则可以使用不同的方式实现,例如使用 Lambda 函数,或者使用类成员函数等。回调函数实现方式提供了更加灵活的处理方式,能够非常有效地扩展责任链模块的通用性能,代码如下:
template< typename Func_t,typename...Params >
void requestCallback(Func_t&&fun,Params&&...args){

利用 std::result_of<> 元函数推导函数的返回值,并对函数的返回值进行检查以保证函数的返回值是布尔类型。类型检查使用 std::is_same 元函数,当 result_type 的返回值是 bool 类型时,is_same 的返回值就是 true。将推断出来的函数返回值和 bool 类型进行匹配性检查,如果返回值不符合要求,则在编译的过程中报出错误并停止编译,代码如下:
//designM/rspsLink.hpp

    using result_type = typename std::result_of<
        Func_t(itfc_t * ,Params&&...) >::type;
    static_assert(std::is_same<result_type,bool >::value,"");

    for(auto item:m_link__){
        auto * p = item;
        if(p == nullptr)continue;
        //调用回调函数,使用每个handler对数据进行处理
        bool rst = fun(p,std::forward<Params >(args)...);
        if(rst == false){ //当处理方法的返回值是false时结束循环
             break;
        }
    }
}

push_back() 方法和 insert() 方法用于在责任链上添加处理对象,这两种方法都是模板方法。push_back() 在链尾上增加而 insert 在指定位置添加,代码如下:
//designM/rspsLink.hpp

template< typename subType >
void push_back(subType * rsps)
{
    using type = typename std::remove_pointer<
                  typename std::decay<subType >::type >::type
    static_assert(std::is_base_of<private__::respItfc__,type >::value,"");
    m_link__.push_back(rsps);
}

template< typename subType >
void insert(iterator it,std::shared_ptr< subType > rsps)
{
    using type = typename std::remove_pointer<
                 typename std::decay<subType >::type >::type
    static_assert(std::is_base_of<private__::respItfc__,type >::value,"");
    m_link__.insert(it,rsps);
}

两个 erase() 方法用来移除指定位置的处理对象。这两种方法能够用来移除一个或者多个处理对象,代码如下:
    void erase(iterator it)
    {
        m_link__.erase(it);
    }
    void erase(iterator start,iterator end)
    {
         m_link__.erase(start,end);
    }
    iterator begin(){return m_link__.begin();}
    iterator end(){return m_link__.end();}
};

C++责任链模式应用示例

在下面的示例中使用宏定义了接口,并采用了两种请求方式分别执行任务。接口函数的参数使用两个 int 类型,代码如下:
//respLink.cpp
#include<iostream >
#include"designM/rspsLink.hpp"
using namespace wheels;
using namespace dm;

DECLARE_RESPLINK_ITFC_DEFAULT(respItfc,int,int)

使用宏 DECLARE_RESPLINK_ITFC_DEFAULT 定义接口,接口的名字为 respItfc,接口需要两个 int 类型的参数。宏 RESPLINK_ITFC_MTD 是一个可变参的宏函数,可以使用任意多个不同数量和类型的数据类型参数,例如可以使用 const int* 和 int& 等。

上面的宏定义展开后的结构如下:
//respLink.cpp

struct respItfc:public private__::respItfc__{
    virtual bool operation(int,int) = 0;
};

接下来实现两个 concreteHandler,分别是 Implementation1、Implementation2,这两个类都需要从 respItfc 继承,并实现纯虚函数 operation,代码如下:
//respLink.cpp
#include <iostream>

struct respItfc {
    virtual bool operation(int data, int b) = 0;
    virtual ~respItfc() = default;
};

struct Implementation1 : respItfc {
    bool operation(int data, int b) override {
        std::cout << "Implementation1: " << data << " data b: " << b << std::endl;
        return true;
    }
};

struct Implementation2 : respItfc {
    bool operation(int data, int b) override {
        std::cout << "Implementation2: " << data << " data b: " << b << std::endl;
        return false;
    }
};

template<class T>
class rspsLink {
public:
    void push_back(T* handler) { /* 略 */ }
    void request(int a, int b) { /* 略 */ }
    template<typename F>
    void requestCallback(F&& f, int a, int b) { /* 略 */ }
};

int main() {
    rspsLink<respItfc> link;
    // 实例化责任链模式模块
    Implementation1 impl1;
    // 实例化两个 handler
    Implementation2 impl2;
    link.push_back(&impl1);
    // 将两个 handler 按照顺序加入责任链
    link.push_back(&impl2);
    link.request(123, 789);
    // 使用 request 方法执行请求

    // 使用回调函数的方式执行请求,回调函数的第1个参数是接口指针
    // 然后通过虚函数的多态功能分别调用两个不同的 handler
    link.requestCallback([](respItfc* item, int a, int b) -> bool {
        std::cout << "call in lambda function" << std::endl;
        return item->operation(a, b);
    }, 123, 789);

    return 0;
}
运行结果如下:

Implementation1:123 data b:789
Implementation2:123 data b:789
call in lambda function
Implementation1:123 data b:789
call in lambda function
Implementation2:123 data b:789

相关文章