「这是我参与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
函数,对不同的类型:int
、double
和 string
做加运算,而不用针对每种类型各写一个 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)));
}
});
}
复制代码
以上二者的主要区别如下,只有标红的部分逻辑不太相同
此时我们就需要使用模板(泛型)函数类简化以上逻辑了,修改优化后的代码如下
/// @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 只修改一个地方,也间接的让外面的调用接口都充当了测试函数,提高了程序的健壮性。
近期评论