C++11简化代码中的重复逻辑

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

参加该活动的第 24 篇文章

不同的类型具有相同的行为

简单的情况下,我们可以使用模板(泛型)类型来简化代码中的逻辑,举个例子

template <typename T>
auto Add(T a, T b) -> decltype(a + b)
{
    return a + b;
}

void TestAdd()
{
    auto r1 = Add(1, 2);                       ///< 3
    auto r2 = Add(1.2, 3.5);                   ///< 4.7
    auto r3 = Add(string("aa"), string("bb")); ///< aabb
}
复制代码

以上代码中,我们通过一个模板 Add 函数,对不同的类型:intdoublestring 做加运算,而不用针对每种类型各写一个 Add 函数,复用了代码、消除了重复逻辑

本质上是通过模板类型 T 擦出了类型,使得我们可以复用这些不同类型的共同行为。但是模板类型并不是万能的,有些情况下就不适用。比如,两个函数中大部分逻辑都相同,但只是某个处理步骤略有不同,此时仅通过 T 就无能为力了。因为泛型类型 T 只能擦除类型,而不能屏蔽不同的行为

部分行为相同,部分行为不同

举个例子,假设有两个解析函数

/// @note 遍历 xml 文件,将标签 IDAttr 的值和 AlgorithmTypeAttr 属性值组成键值对,然后存到 map 中
void ParseA(const string &confFile, const string &xmlPath, unordered_multimap<uint32_t, int> &map)
{
    property_tree::ptree ptree;
    property_tree::read_xml(confFile, ptree);
    auto kpichilds = ptree.get_child(xmlPath);
    std::for_each(kpichilds.begin(), kpichilds.end(), [this, &map](const ptree::value_type &pair)
                  {
                      string str = pair.second.get<string>(IDAttr);
                      uint32_t key = strtol(str.data(), nullptr, 16);

                      int val = pair.second.get<int>(AlgorithmTypeAttr);
                      map.insert(make_pair(key, val));
                  });
}
复制代码
/// @note 遍历 xml 文件,将标签 AlgorithmAttr 属性值和其子节点的标签 Attr 属性值组成键值对,然后存入 map 中
void ParseB(const string &confFile, const string &xmlPath, unordered_multimap<uint32_t, string> &map)
{
    property_tree::ptree ptree;
    property_tree::read_xml(confFile, ptree);
    auto kpichilds = ptree.get_child(xmlPath);
    std::for_each(kpichilds.begin(), kpichilds.end(), [&map](const ptree::value_type &pair)
                  {
                      int type = pair.second.get<int>(AlgorithmAttr);
                      auto childs = pair.second.get_child(Tag);
                      for (auto item : childs)
                      {
                          bap.insert(make_pair(type, item.second.get<string>(Attr)));
                      }
                  });
}
复制代码

以上二者的主要区别如下,只有标红的部分逻辑不太相同

image.png

此时我们就需要使用模板(泛型)函数类简化以上逻辑了,修改优化后的代码如下

/// @note 通过泛型函数 F 屏蔽行为上的差异,使得各个函数可以复用这段代码
template <typename F>
void Parse(const string& confFile, const string& xmlPath, const F& f) {
    property_tree::ptree ptree;
    property_tree::read_xml(confFile, ptree);
    auto kpichilds = ptree.get_child(xmlPath);
    std::for_each(kpichilds.begin(), kpichilds.end(), f);
}

/// @note 内部复用 Parse 行为
void ParseA(unordered_multimap<uint32_t, int>& map) {
    Parse(ConfFileName, KPIPath, [this, &map](const ptree::value_type& pair) {
        /// @note 差异的行为
        string str = pair.second.get<string>(IDAttr);
        uint32_t key = strtol(str.data(), nullptr, 16);

        int val = pair.second.get<int>(AlgorithmTypeAttr);
        map.insert(make_pair(key, val));
    };);
}

/// @note 内部复用 Parse 行为
void ParseB(unordered_multimap<uint32_t, string>& map) {
    Parse(ConfFileName, KPIPath, [this, &map](const ptree::value_type& pair) {
        /// @note 差异的行为
        int type = pair.second.get<int>(AlgorithmAttr);
        auto childs = pair.second.get_child(Tag);
        for (auto item : childs)
        {
            bap.insert(make_pair(type, item.second.get<string>(Attr)));

        }

    };);
}

复制代码

显而易见,泛型函数 F 屏蔽了部分行为上的差异,使得内部逻辑保持一致,仍然可以复用相同逻辑的代码,具体的有差异的行为在外面定义。消除重复逻辑使得我们的代码具有较高的内聚性,可以做到一个 bug 只修改一个地方,也间接的让外面的调用接口都充当了测试函数,提高了程序的健壮性