C++应用程序/程序设置类?

发布于 2024-11-15 23:46:21 字数 935 浏览 1 评论 0原文

将 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();

现在我正在进入单元测试,并且我开始意识到静态类是不可取的。所以我想把它变成一个基于实例的类。但我必须管理嵌套类实例,这很简单,但对于测试来说仍然有点麻烦。嵌套类的全部目的只是将设置分组为逻辑组。我正在争论基于字符串的设置类是否会更好,例如 settings->get("General.HomePage") 尽管我认为我更喜欢专用访问器方法的强类型。

那么,为了回答我的问题,什么是保存支持简单单元测试的程序配置/设置的良好数据结构?

Using Visual Studio C++ with MFC. I'm trying to figure out what would be a good way to store application/program settings. I'm not referring to their persistent storage but to the data structure used in code to hold the settings.

I created a static class called Settings that has several static methods and also nested classes to partition the settings. For example:

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

Then I can access my settings throughout my code like:

Settings::General::GetHomePage();

Now I'm getting into unit testing and I'm starting to realize that static classes are undesirable. So I want to turn this into an instance based class. But I'll have to manage the nested class instances which is trivial but still seems a little cumbersome for testing. The whole intention of the nested classes is simply to group the settings into logical groups. I'm debating whether a string-based settings class would be better, something like settings->get("General.HomePage") although I think I prefer the strong typing of dedicated accessor methods.

So to get to my question what is a good data structure to hold program configuration/settings that supports straightforward unit testing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

晒暮凉 2024-11-22 23:46:21

如果这对您有用,您可以这样做。您可以放弃枚举并使用 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;
}

You can do this if it works for you. You can ditch the enum and go to const strings or even free-form strings. The enum doesn't really have to be defined in the class either. There are lots of ways to do it.

Another class could do something similar with multiple instances using a template to define the enum type if you wanted to implement categories.

Just an idea.

#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;
}
南街女流氓 2024-11-22 23:46:21

我认为您的要求之间不一定存在冲突:(1)提供对配置变量的类型安全访问; (2) 使用“complete.scoped.name”语法来指定配置变量的名称。当然,您可以进行类型安全的操作,例如:

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

您可能会通过阅读《入门指南》的第 2 章和第 3 章找到一些灵感 (PDF, HTML) 为我的 Config4Cpp 库。

编辑:我提到的 Config4Cpp 文档可能会为 API 设计提供灵感,但我迟来的意识到,如果您决定从头开始编写自己的配置类(而不是使用第三方库(例如 Config4Cpp)...

您的类应该使用 std::map 来存储 completelyScopedName->value 映射的集合。显然,fullScopedName 将是一个字符串,但有两个选项可用于表示

第一个选项是将表示为字符串。类型安全的访问器(例如 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;
}

I don't think there has to be a conflict between your requirements of: (1) providing type-safe access to configuration variables; and (2) using a "fully.scoped.name" syntax to specify the name of a configuration variable. Surely you could have type-safe operations such as:

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

You might find some inspiration by reading Chapters 2 and 3 of the Getting Started Guide (PDF, HTML) for my Config4Cpp library.

Edit: The Config4Cpp documentation I mentioned might provide inspiration for API design, but I belated realised that you might appreciate advice on implementation options in case you decide to write your own configuration class from scratch (rather than use a third-party library like Config4Cpp) ...

Your class should use a std::map to store a collection of fullyScopedName->value mappings. Obviously, the fullyScopedName will be a string, but there are two options for representing the value.

The first option is to represent the value as a string. A type-safe accessor such as getInt() or getBool() will retrieve the string-based value from the map and then parse it to convert it into the desired type. If the parsing fails, then the accessor operation throws an exception. (That is the approach taken by Config4Cpp.)

The second option is to represent value as shown in the pseudocode below:

enum ValueType { STRING_VAL, INT_VAL, BOOL_VAL };
struct Value {
    ValueType         type;
    union {
        const char *  stringVal;
        int           intVal;
        bool          boolVal;
    } data;
};

The implementation of a type-safe accessor can then be coded as follows (pseudocode):

int getInt(const char * fullyScopedName)
{
    Value * v = nameValueMap[fullyScopedName];
    if (v->type != INT_VAL) {
        throw WrongTypeException(...);
    }
    return v->data.intVal;
}
温柔戏命师 2024-11-22 23:46:21

这是我现在使用的类,主要受到 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_;
};

This is the class that I'm using now mostly inspired by Nathan's answer except with templated methods:

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_;
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文