2011-06-16 26 views
1

在MFC中使用Visual Studio C++。我试图找出什么是存储应用程序/程序设置的好方法。我并不是指它们的持久存储,而是指代码中用于保存设置的数据结构。应用程序/程序设置的C++类?

我创建了一个名为Settings的静态类,它有几个静态方法和嵌套类来分区设置。例如:

Settings::General::GetHomePage(); 

现在我进入单元测试,我开始认识到静态类是不可取的:

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-> get(“General.HomePage”),尽管我认为我更喜欢专用存取方法的强类型。

所以为了解决我的问题什么是一个好的数据结构来保存支持直接单元测试的程序配置/设置?

回答

3

你可以做到这一点,如果它适合你。你可以抛开枚举并转到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; 
} 
+0

在C++中恕我直言,使用'typedef enum bla {} foo'确实没有意义。声明变量时,不需要编写'enum bla varname'。 'bla varname'就够了。 – RedX 2011-06-17 07:51:40

+0

@RedX:在这个玩具的例子中,我同意。然而,通常情况下,随着代码的发展,你会发现你的模板参数并不适合你的需求。将SettingCollection :: mapped_type或SettingCollection :: key_type引用为类型会更容易,因此如果在复杂的代码段中更改模板参数,则只能在一个点上更改类型。如果您在本身是模板的类中使用了模板类型,这也很有效。它还显示了该类型的用途,因为它在整个代码中都被明确地用于地图中。 – Nathan 2011-06-17 17:43:23

2

我不认为必须有你的要求之间的冲突:(1)配置变量提供类型安全访问;和(2)使用"fully.scoped.name"语法来指定配置变量的名称。当然,你可以有类型安全的操作,例如:通过读取第二章和第三章的的3

const char * getString(const char * fullyScopedName); 
int   getInt(const char * fullyScopedName); 
bool   getBool(const char * fullyScopedName); 

你可能会找到一些灵感入门指南PDFHTML)我Config4Cpp库。

编辑:本Config4Cpp文档我提到可能会提供API的设计灵感,但我迟来的意识到你可能的情况下,您决定从头开始编写自己的配置类上实现选项欣赏意见(而不是使用第三方三方库像Config4Cpp)...

你的类应该使用std::map存储的fullyScopedName->值映射的集合。显然,fullyScopedName将是一个字符串,但有两种选择来表示

第一个选项是将表示为字符串。类型安全的访问器(如getInt()getBool())将从映射中检索基于字符串的值,然后解析它以将其转换为所需的类型。如果解析失败,那么访问器操作会引发异常。 (即由Config4Cpp采取的做法。)

第二个选项是代表如下面的伪代码:

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; 
} 
0

这是我使用的类现在大多由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_; 
};