Класс C ++ для настроек приложения / программы? - PullRequest
3 голосов
/ 17 июня 2011

Использование 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"), хотя я думаю, что предпочитаю строгую типизацию специальных методов доступа.

Итак, чтобы перейти к моему вопросу, что такое хорошая структура данных для хранения конфигурации / настроек программы, которая поддерживает простое модульное тестирование?

Ответы [ 3 ]

4 голосов
/ 17 июня 2011

Вы можете сделать это, если это работает для вас. Вы можете отказаться от enum и перейти к строкам const или даже к строкам произвольной формы. Перечисление не обязательно должно быть определено в классе. Есть много способов сделать это.

Другой класс может сделать что-то подобное с несколькими экземплярами, используя шаблон для определения типа enum, если вы хотите реализовать категории.

Просто идея.

#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;
}
2 голосов
/ 17 июня 2011

Я не думаю, что между вашими требованиями должен быть конфликт: (1) обеспечения безопасного доступа к переменным конфигурации; и (2) использование синтаксиса "fully.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 для хранения коллекции отображений fullScopedName-> 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;
}
1 голос
/ 21 июня 2011

Этот класс, который я использую сейчас, в основном вдохновлен ответом Натана, за исключением шаблонных методов:

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