我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。
这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。
源码指引:github源码指引_初级代码游戏的博客-CSDN博客
处理配置文件是我们经常要面对的工作。处理配置文件当然有各种不同的办法,这里主要讨论的是如何设计读写配置项的接口来简化代码。
目录
一、全动态操作配置项不是好主意
我们当然可以写一个全动态的接口,比如getConigItem("name")来获取一个配置项,这给代码带来了足够的灵活性,增加配置项不需要修改其他代码,只需要在使用的地方直接用配置项名字调用即可。
这种方式灵活但缺点很多,时间长了搞不清楚到底哪些配置项已经不再使用了,清理配置成了极大的冒险,最后结果是只增不删,配置文件越来越大。
二、静态化配置项
配置项用全局变量来存储一般是没什么问题的,用全局变量来存储的好处是,凡用到的配置项必须对应一个变量,想找到一个配置项在哪些地方使用只需要直接搜变量名。清理也很简单,删掉变量,代码能够编译就不会有问题。
三、配置项读写接口的合并
处理配置项有个难题,读写代码是分离的,增加配置项需要分别写两处,配置项名称(写在文件里的名字,字符串)写错了就出问题,而且不容易检查。如果将名称用宏来定义,两处就变成了三处,用错了宏仍然难以检查,所以并不解决问题。
解决这个问题需要一点编程技巧,比如实现下面这样的配置项定义操作:
struct AppConfig
{
long max_log_file_size = 0;//日志文件最大大小
long max_data_file_size = 0;//数据文件最大大小
vector<IConfigItem *> m_ConfigItems;
AppConfig()
{
m_ConfigItems.push_back(new ConfigItem_long("max_log_file_size",&max_log_file_size));
m_ConfigItems.push_back(new ConfigItem_long("max_data_file_size", &max_data_file_size));
}
。。。。。。
配置文件的读写是通用代码,和具体配置项没有关系(后面会列出代码)。
IConfigItem:配置项接口。
ConfigItem_long:整数型配置项的实现,继承自IConfigItem。
上面的代码具有如下好处:
- 每个配置项是一个变量,很容易搜索,很容易找到使用位置,很容易发现废弃的配置项
- 每个配置项的定义只用一行代码解决,将配置项名称和对应变量关联起来,很容易检查
- 文件读写由通用代码完成,不依赖具体配置项
四、通用读写代码
下面的代码是使用cJSON操作json文件的代码:
struct AppConfig
{
。。。。。。
bool Load()
{
bool ret = true;
//读取配置文件
{
string filename = AppConfigFileName;
CEasyFile file;
string str;
if (!file.ReadFile(filename.c_str(), str))
{
thelog << "未能打开应用配置文件 " << filename << ende;
return false;
}
cJSON* cjson = cJSON_Parse(str.c_str());
if (NULL == cjson)
{
thelog << "解析配置出错 " << filename << endl << str << ende;
return false;
}
ret = true;
cJSON* cjson_data = cJSON_GetObjectItem(cjson, "data");
if (NULL == cjson_data)
{
thelog << "解析配置出错,没有data项 " << ende;
ret = false;
}
else
{
//CJsonUtil::_GetJsonParam(cjson_data, "max_log_file_size", max_log_file_size);
//CJsonUtil::_GetJsonParam(cjson_data, "max_data_file_size", max_data_file_size);
for (auto& v : m_ConfigItems)
{
v->LoadConfigItem(cjson_data);
thelog << v->name << " " << v->ToString(str) << endi;
}
}
cJSON_free(cjson);
}
//检查
if (max_log_file_size < 1024)
{
thelog << "参数max_log_file_size设置过小 " << max_log_file_size << ende;
max_log_file_size = 1024;
}
if (max_data_file_size < 1024)
{
thelog << "参数max_data_file_size设置过小 " << max_data_file_size << ende;
max_data_file_size = 1024;
}
return ret;
}
bool Save()
{
string filename = AppConfigFileName;
cJSON* cjson = cJSON_CreateObject();
cJSON* cjson_data = cJSON_AddObjectToObject(cjson, "data");
//cJSON_AddNumberToObject(cjson_data, "max_log_file_size", max_log_file_size);
//cJSON_AddNumberToObject(cjson_data, "max_data_file_size", max_data_file_size);
for (auto& v : m_ConfigItems)
{
v->SaveConfigItem(cjson_data);
}
string str = cJSON_Print(cjson);
CEasyFile file;
if (!file.WriteFile(filename.c_str(), str.c_str()))
{
thelog << "写配置文件失败 " << filename << ende;
return false;
}
cJSON_free(cjson);
return true;
}
。。。。。。
这段代码就是很普通的json读写代码,格式为一个名为data的数组,数组里面是每个配置。
关键点是对每个配置项调用IConfigItem::LoadConfigItem或IConfigItem::SaveConfigItem来实现读写。所以后面的重点是IConfigItem是什么样的。
代码中屏蔽掉的就是原来的代码,对每个配置项都需要一行来读、一行来写,遗漏了、写错了名字或变量都是难以在编译时发现的,如果配置项不够关键,可能很久都不会发现错误。
五、配置项接口IConfigItem
IConfigItem定义如下:
class IConfigItem
{
public:
string name;//名称
IConfigItem(char const* _name) :name(_name) {}
virtual bool LoadConfigItem(cJSON*) = 0;
virtual bool SaveConfigItem(cJSON*) = 0;
virtual string& ToString(string& ret) = 0;
};
这个接口包含了配置项的名字,但是没有包含具体的值。为了支持不同的配置项的数据类型,需要继承子类来分别实现(好的程序员应该不会对编写困难但写好一次可以到处使用的代码感到厌烦)。
六、几种不同数据类型配置项的实现
以下代码可能遇到我自己写的辅助函数,虽然没有给出代码,但是根据名字就能猜到是如何实现的。
整数类型:
struct ConfigItem_long : public IConfigItem
{
long* pValue;//指向存储位置
ConfigItem_long(char const* _name, long* _pValue) :IConfigItem(_name), pValue(_pValue) {}
virtual bool LoadConfigItem(cJSON* cjson_data)override
{
return CJsonUtil::_GetJsonParam(cjson_data, name.c_str(), *pValue);
}
virtual bool SaveConfigItem(cJSON* cjson_data)override
{
return cJSON_AddNumberToObject(cjson_data, name.c_str(), *pValue);
}
virtual string& ToString(string& ret)override
{
char buf[64];
sprintf(buf, "%ld", *pValue);
return ret = buf;
}
};
注意,pValue是个指针,指向实际的long变量。这个指针其实应该是私有的,因为不会被外部调用。
字符串类型:
struct ConfigItem_string : public IConfigItem
{
string* pValue;//指向存储位置
ConfigItem_string(char const* _name, string* _pValue) :IConfigItem(_name), pValue(_pValue) {}
virtual bool LoadConfigItem(cJSON* cjson_data)override
{
return CJsonUtil::_GetJsonParam(cjson_data, name.c_str(), *pValue);
}
virtual bool SaveConfigItem(cJSON* cjson_data)override
{
return cJSON_AddStringToObject(cjson_data, name.c_str(), pValue->c_str());
}
virtual string& ToString(string& ret)override
{
return ret = *pValue;
}
};
完全相似但是具体读写操作有所不同。
还可以编写一些特殊类型,比如加密的参数、经过base64编码的参数。
(这里是文档结束)