在日常的代码编写中经常出现不同数据格式转换的场景,比如给定一个整数,将其与每周的星期名称映射起来,该如何实现呢?
常见的实现途径是通过if-else或者switch-case的方式来实现,如下代码所示:
const std::string GetDayName(const int day) {
std::string dayName = "";
if(day == 1) {
dayName = "星期一";
} else if(day == 2) {
dayName = "星期二";
} else if(day == 3){
dayName = "星期三";
} else if(day == 4) {
dayName = "星期四";
} else if(day == 5) {
dayName = "星期五";
} else if (day == 6) {
dayName = "星期六";
} else if(day==0) {
dayName = "星期日";
}
return dayName;
}
这样的代码优势是简单,初学者也可以写出这样的代码;代码的问题在于: 1) 代码太长,逻辑重复冗余,复杂度高; 2) 可维护性低,耦合性强,每新增一个流程分支时就要在函数代码中添加一个判断语句。
如何解决写出更加优雅的代码来消除if-else/switch-case语句,表驱动法(Table-Driven Approach)是一种可选的方法。表驱动的方法是指把数据信息放置表中,通过查表的方法获取数值的方法。表驱动的代码如下:
#include <cassert>
#include <string>
const std::array<std::string, 7> dayNames = {"星期日","星期一","星期二","星期三","星期四","星期五","星期六"};
const std::string GetDayName(const int day) {
assert(day >= 0);
assert(day <= 6);
return dayNames[day];
}
通过几行代码就可以替代冗长的if-else代码,使得代码不仅简单明了,而且也方便代码维护。
上述表驱动方法虽然对于消除长的if-else语句、提高代码质量很有用,但是一般的表驱动难以重用。因为不同的业务有不同的场景,不同的逻辑分支,这些都导致上述的表驱动的方式实现不够通用。
实现一个通用的表驱动模式(Table-Driven Approach)需要解决两个问题:
1)如何在表中注册不同类型的执行函数。也许有人会说是不是可以采用C++的函数包装器std::function,但是在实际应用中,执行函数的形参不尽相同,但std::function的参数类型在一开始就确定了。
2)在表中找到对应的执行函数之后如何调用执行函数。由于每个函数的形参不尽相同,如何以统一的方式调用也是一个问题;
问题2)可以采用C++ 11的可变模板参数解决;问题1)需要使用C++ boost::Any来解决。通用的表驱动C++11实现支持各种类型的key,执行函数支持普通函数、函数对象、lamda表达式和成员函数。
#include <iostream>
#include <array>
#include <map>
#include <functional>
#include <type_traits>
#include <boost/any.hpp>
#include <typeinfo>
template <typename Key>
class TableDriver {
public:
template <typename... Args, typename Func>
void Register(const Key &key, Func &&func) {
typedef typename std::result_of<Func(Args...)>::type rettype;
auto f = std::function<rettype(Args && ...)>(
[=](Args &&... args) { return func(std::forward<Args>(args)...); });
m_map[key] = f;
}
template <typename R = void, typename... Args>
R Execute(const Key &key, Args &&... args) {
auto it = m_map.find(key);
if (it == m_map.end())
return R();
auto f = boost::any_cast<std::function<R(Args && ...)>>(it->second);
return f(std::forward<Args>(args)...);
}
template <typename R = void> R Execute(const Key &key) {
auto it = m_map.find(key);
if (it == m_map.end())
return R();
auto f = boost::any_cast<std::function<R()>>(it->second);
return f();
}
private:
std::map<Key, boost::any> m_map;
};
测试代码:
struct Tdd {
int Test(int x) {return x + 2;}
};
int main() {
TableDriver<std::string> dv;
dv.Register("aa", []{std::cout << "aa test;" << std::endl;});
dv.Execute("aa");
int y = 0;
dv.Register<int, int>("aa", [](const int& a, const int& b) {return a + b;});
auto t = dv.Execute<decltype(y)>("aa", 3, 4);
std::cout << t << std::endl;
Tdd a;
dv.Register<int>("aa", [&a](int x) {return a.Test(x);});
auto t1 = dv.Execute<int>("aa", 3);
std::cout << t1 << std::endl;
dv.Register<std::string>("aa", [](const std::string& x) {return x;});
auto t2 = dv.Execute<std::string>("aa", std::string("test"));
std::cout << t2 << std::endl;
return 0;
}
函数执行结果:
aa test;
7
5
test
然后我们可以重写开头的格式转换代码:
TableDriver<int> dv;
const std::string GetDayName(const int& day) {
return dv.Execute<std::string>(day);
}
int main() {
dv.Register(0, []()->std::string{return "星期日";});
dv.Register(1, []()->std::string{return "星期一";});
dv.Register(2, []()->std::string{return "星期二";});
dv.Register(3, []()->std::string{return "星期三";});
dv.Register(4, []()->std::string{return "星期四";});
dv.Register(5, []()->std::string{return "星期五";});
dv.Register(6, []()->std::string{return "星期六";});
int day = 3;
std::cout << GetDayName(day) << std::endl;
return 0;
}
https://www.cnblogs.com/qicosmos/p/3146402.html