配置模块
一般而言一个配置模块应该具备以下几点基本功能
- 支持定义/声明配置项
- 支持动态更新配置项的值
- 支持从指定路径加载配置项
- 支持给配置项注册配置变更通知
- 支持给配置项设置校验方法
- 支持导出当前配置
GitHub
- https://github.com/huxiaohei/tiger.git
设计
tiger
框架中的配置模块采用约定优于配置的设计思想
约定优于配置,也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。
本质是说,开发人员仅需规定应用中不符约定的部分。例如,如果模型中有个名为Sale的类,那么数据库中对应的表就会默认命名为sales。只有在偏离这一约定时,例如将该表命名为"products_sold",才需写有关这个名字的配置。如果所用工具的约定与期待相符,便可省去配置;反之,可以配置来达到所期待的方式。 ---- 维基百科
tiger
的配置模块使用了yaml-cpp
作为YAML
解析库,关于yaml-cpp
的编译以及使用方法可以点击这里,关于YAML
格式介绍可以点击这里
实现
配置模块中主要包括ConfValBase
、ConfigVar
、Config
三个类,以及用于各种类型转换的LexicalCast
模版类
ConfValBase
为配置项值的虚基类,定义了配置项公有的成员和方法。
- 配置项名称和配置项描述
- 将配置项值序列化为
string
和将string
反序列化为配置项对应的值
ConfigVar
ConfigVar
为继承ConfValBase
类的一个模版类,具备一个模版参数T
,即配置项值类型
- 在调用
from_string
方法时会实例化一个对应的LexicalCast<std::string, T>
对象并执行对应的operator
方法,将string
反序列化为T
类型的值 - 在调用
to_string
方式时则会实例化一个对应的LexicalCast<T, std::string>
对象并执行对应的operator
方法,将T
类型的值序列化为string
Config
Config
为ConfigVar
管理类,采用单例设计模式负责管理所有的ConfigVar
对象
- 提供
Lookup
方法用于查询配置,如果在查询时没有查到对应的值,则会新建一个配置项,并使用查询时的默认值参数。因此也保证了配置项定义即可用的特性。 - 提供
LoadFromYmal
方法用于加载YAML
配置文件,加载配置文件的同时也会更新已经存在的配置项
LexicalCast
LexicalCast
为一个模版类template <typename FromType, typename ToType>
,因其重载()
运算符,其也是一个仿函数。根据FromType
和ToType
的不同,LexicalCast
具有多种偏特化实现,而每一种偏特化实现都是将FromType
类型转换为ToType
类型
-
基本类型转换
template <typename FromType, typename ToType> class LexicalCast { public: ToType operator()(const FromType &v) { return boost::lexical_cast<ToType>(v); } };
这里的
LexicalCast
类是一个仿函数,它支持LexicalCast<FromType, ToType>()(const FromType &v)
调用,可将传入的FromType
类型的参数v
进行转换,并返回ToType
类型的结果 -
vector
类型转换template <typename T> class LexicalCast<std::string, std::vector<T>> { public: std::vector<T> operator()(const std::string &str) { YAML::Node root = YAML::Load(str); std::vector<T> v; std::stringstream ss; for (size_t i = 0; i < root.size(); ++i) { ss.str(""); ss << root[i]; v.push_back(LexicalCast<std::string, T>()(ss.str())); } return v; } }; template <typename T> class LexicalCast<std::vector<T>, std::string> { public: std::string operator()(const std::vector<T> &v) { YAML::Node node; for (const auto &it : v) { node.push_back(LexicalCast<T, std::string>()(it)); } std::stringstream ss; ss << node; return ss.str(); } };
可见在将
string
与std::vector<T>
类型相互转换的时,如果T
不是基本类型,那么将会继续实例化特定模版,直到T
为基础类型 -
其余类型转换
其余类型转换与vector
类型转换原理一致,详细请参考源码
每添加一个新类型的转换,那这个类型和之前已实现的类型组合出的数据类型也可以顺利转换。比如新加map
类型转换,那么就可以组合出 vector<map>
、 map<vector>
,map<map>
等。这种基于偏特化实现类型转换方法,可将代码做到高度简化,但功能却非常强大,并且后续维护和扩展非常方便。这也展示了泛型程序设计的强大