将Visual Studio C ++与MFC一起使用。我想弄清楚什么是存储应用程序/程序设置的好方法。我不是指他们的持久存储,而是指代代码中用来保存设置的数据结构。
我创建了一个名为Settings的静态类,它有几个静态方法,还有嵌套类来对设置进行分区。例如:
class Settings
{
public:
Settings(void);
~Settings(void);
static void SetConfigFile(const char * path);
static CString GetConfigFilePath();
static void Load();
static void Save();
class General
{
public:
static CString GetHomePage();
static void SetHomePage(const char * url);
private:
static int homePageUrl_;
};
private:
static CString configFilePath_;
};
然后我可以在我的代码中访问我的设置,例如:
Settings::General::GetHomePage();
现在我进入单元测试,我开始意识到静态类是不受欢迎的。所以我想把它变成一个基于实例的类。但是我必须管理嵌套的类实例,这些实例虽然微不足道,但对于测试来说仍然有点麻烦。嵌套类的全部意图只是将设置分组为逻辑组。我辩论基于字符串的设置类是否会更好,像设置 - >获得(“General.HomePage”),但我想我更喜欢的专用访问方法的强类型
所以,让我的问题是什么是一个很好的数据结构来保存程序配置/设置,支持简单的单元测试?
答案 0 :(得分:4)
如果适合您,您可以这样做。您可以抛弃枚举并转到const字符串甚至是自由形式的字符串。枚举也不一定要在类中定义。有很多方法可以做到。
如果你想实现类别,另一个类可以使用模板来定义枚举类型,从而对多个实例做类似的事情。
只是一个想法。
#include "stdafx.h"
#include <map>
#include <string>
#include <iostream>
using namespace std;
class Settings
{
public:
typedef enum
{
HomePageURL,
EmailAddress,
CellPhone
} SettingName;
private:
typedef map<SettingName, string> SettingCollection;
SettingCollection theSettings;
public:
string& operator[](const SettingName& theName)
{
return theSettings[theName];
}
void Load ()
{
theSettings[HomePageURL] = "http://localhost";
}
void Save ()
{
// Do whatever here
}
};
int _tmain(int argc, _TCHAR* argv[])
{
Settings someSettings;
someSettings.Load ();
cout << someSettings [Settings::SettingName::HomePageURL] << endl;
return 0;
}
答案 1 :(得分:2)
我认为您的要求之间不存在冲突:(1)提供对配置变量的类型安全访问; (2)使用"fully.scoped.name"
语法指定配置变量的名称。当然,您可以进行类型安全的操作,例如:
const char * getString(const char * fullyScopedName);
int getInt(const char * fullyScopedName);
bool getBool(const char * fullyScopedName);
您可以通过阅读我的PDF库的入门指南(HTML,Config4Cpp)的第2章和第3章找到一些灵感。
编辑:我提到的Config4Cpp文档可能为API设计提供了灵感,但我迟到意识到您可能会欣赏有关实现选项的建议,以防您决定从头开始编写自己的配置类(而不是而不是像Config4Cpp那样使用第三方库...
您的班级应使用std::map
来存储 fullyScopedName-&gt;值映射的集合。显然, fullyScopedName 将是一个字符串,但有两个选项可用于表示值。
第一个选项是将值表示为字符串。类型安全的访问器(例如getInt()
或getBool()
)将从映射中检索基于字符串的值,然后解析它以将其转换为所需的类型。如果解析失败,则访问者操作会引发异常。 (这是Config4Cpp采用的方法。)
第二个选项是表示 value ,如下面的伪代码所示:
enum ValueType { STRING_VAL, INT_VAL, BOOL_VAL };
struct Value {
ValueType type;
union {
const char * stringVal;
int intVal;
bool boolVal;
} data;
};
然后可以按如下方式编码类型安全访问器的实现(伪代码):
int getInt(const char * fullyScopedName)
{
Value * v = nameValueMap[fullyScopedName];
if (v->type != INT_VAL) {
throw WrongTypeException(...);
}
return v->data.intVal;
}
答案 2 :(得分:1)
这是我现在使用的课程,主要受到Nathan的答案的启发,除了模板化的方法:
class Settings {
public:
Settings(void);
virtual ~Settings(void);
enum SettingName { General_WindowWidth, General_HomePageUrl,
General_ShowDownloadsWindow, Privacy_RememberPasswords,
Privacy_RememberHistory };
virtual void SetConfigFile(const char * path);
virtual std::string GetConfigFilePath();
virtual void Load();
virtual void Save();
template<class T>
T Get(SettingName name) {
return boost::lexical_cast<T>(settings_[name]);
}
template<class T>
void Set(SettingName name, T value) {
settings_[name] = boost::lexical_cast<std::string>(value);
}
void Set(SettingName name, std::string value) {
settings_[name] = value;
}
private:
std::string configFilePath_;
std::map<SettingName, std::string> settings_;
};