Где загрузить и сохранить настройки из файла?


9

Я думаю, что этот вопрос должен относиться к большинству программ, которые загружают настройки из файла. Мой вопрос с точки зрения программирования, и это действительно, как справиться с загрузкой настроек из файла с точки зрения различных классов и доступности. Например:

  • Если у программы был простой settings.iniфайл, следует ли загружать его содержимое в load()метод класса или, возможно, в конструктор?
  • Должны ли значения храниться в public staticпеременных или должны быть staticметоды для получения и установки свойств?
  • Что должно произойти, если файл не существует или не читается? Как бы вы сообщили остальной части программы, что она не может получить эти свойства?
  • и т.п.

Я надеюсь, что я спрашиваю это в нужном месте здесь. Я хотел сделать вопрос как можно более независимым от языка, но я в основном сосредоточился на языках, имеющих такие вещи, как наследование, особенно на Java и C # .NET.


1
Для .NET лучше всего использовать App.config и класс System.Configuration.ConfigurationManager для получения настроек
— Гибсон,

@Gibson Я только начинаю работать в .NET, поэтому не могли бы вы дать мне ссылку на какие-нибудь хорошие учебники для этого класса?
— Энди

Stackoverflow имеет много ответов на него. Быстрый поиск показывает: stackoverflow.com/questions/114527/… и stackoverflow.com/questions/13043530/… Там также будет много информации о MSDN, начиная здесь msdn.microsoft.com/en-us/library/ms228063(v = vs.100) .aspx и msdn.microsoft.com/en-us/library/ms184658(v=vs.110).aspx
— Гибсон,

@Gibson Спасибо за эти ссылки. Они будут очень полезны.
— Энди

Ответы:


8

На самом деле это действительно важный вопрос, и он часто делается неправильно, поскольку ему не уделяется достаточного внимания, хотя он является основной частью практически каждого приложения. Вот мои рекомендации:

Ваш конфигурационный класс, который содержит все настройки, должен быть просто старого типа данных struct / class:

class Config {
    int prop1;
    float prop2;
    SubConfig subConfig;
}

Он не должен иметь методов и не должен включать наследование (если это не единственный выбор, который у вас есть на вашем языке для реализации варианта поля - см. Следующий параграф). Он может и должен использовать композицию для группировки настроек в более мелкие конкретные классы конфигурации (например, subConfig выше). Если вы сделаете это таким образом, то будет идеальным переходить в модульных тестах и ​​в приложении в целом, поскольку оно будет иметь минимальные зависимости.

Скорее всего, вам придется использовать типы вариантов, в случае, если конфигурации для разных настроек неоднородны по структуре. Принято считать, что вам нужно поместить динамическое приведение в какой-то момент, когда вы прочитаете значение, чтобы привести его к нужному (под) классу конфигурации, и, без сомнения, это будет зависеть от другого параметра конфигурации.

Вам не следует лениться вводить все настройки в виде полей, просто выполнив это:

class Config {
    Dictionary<string, string> values;
};

Это заманчиво, так как это означает, что вы можете написать обобщенный класс сериализации, которому не нужно знать, с какими полями он имеет дело, но это неправильно, и я объясню почему через минуту.

Сериализация конфига производится в совершенно отдельном классе. Какой бы API или библиотеку вы не использовали для этого, тело вашей функции сериализации должно содержать записи, которые в основном равносильны отображению пути / ключа в файле к полю на объекте. Некоторые языки обеспечивают хороший самоанализ и могут сделать это для вас из коробки, другие вам придется явно написать отображение, но главное, что вам нужно будет написать отображение только один раз. Например, рассмотрим этот фрагмент, который я адаптировал из документации синтаксического анализатора опций программы c ++ boost:

struct Config {
   int opt;
} conf;
po::options_description desc("Allowed options");
desc.add_options()
    ("optimization", po::value<int>(&conf.opt)->default_value(10);

Обратите внимание, что в последней строке в основном говорится, что «оптимизация» отображается на Config :: opt, а также что есть объявление того типа, который вы ожидаете. Вы хотите, чтобы чтение конфигурации не выполнялось, если тип не соответствует ожидаемому, если параметр в файле на самом деле не является float или int или не существует. Т.е. сбой должен произойти, когда вы читаете файл, потому что проблема связана с форматом / проверкой файла, и вы должны выбросить код исключения / возврата и сообщить точную проблему. Вы не должны откладывать это позже в программе. Вот почему у вас не должно быть соблазна перехватить весь словарь в стиле Conf, как упомянуто выше, который не потерпит неудачу при чтении файла - поскольку приведение откладывается до тех пор, пока не понадобится значение.

Вы должны сделать класс Config доступным только для чтения некоторым способом - устанавливая содержимое класса один раз, когда вы создаете его и инициализируете его из файла. Если вам необходимо иметь динамические настройки в вашем приложении, которые изменяются, а также постоянные, которые этого не делают, у вас должен быть отдельный класс для обработки динамических, а не пытаться разрешить битам вашего класса конфигурации быть не только для чтения ,

В идеале вы читаете файл в одном месте вашей программы, т.е. у вас есть только один экземпляр " ConfigReader". Однако, если вы боретесь с передачей экземпляра Config туда, где вам это нужно, лучше иметь второй ConfigReader, чем вводить глобальный конфиг (который, я предполагаю, означает, что OP означает «статический» "), что подводит меня к следующему пункту:

Избегайте соблазнительной сирены: «Я избавлю вас от необходимости проходить этот урок, все ваши конструкторы будут красивыми и чистыми. Продолжайте, это будет так просто». Правда в том, что с хорошо разработанной тестируемой архитектурой вам вряд ли понадобится проходить класс Config или его части через множество классов вашего приложения. То, что вы найдете в своем классе верхнего уровня, своей функции main () или что-то еще, вы распутаете conf в отдельные значения, которые вы предоставите своим классам компонентов в качестве аргументов, которые вы затем соберете вместе (зависимость вручную) инъекции). Одноэтапный / глобальный / статический conf значительно усложнит реализацию и понимание модульного тестирования вашего приложения - например, он запутает новых разработчиков в вашей команде, которые не будут знать, что им нужно устанавливать глобальное состояние для тестирования.

Если ваш язык поддерживает свойства, вы должны использовать их для этой цели. Причина в том, что это означает, что будет очень легко добавить «производные» параметры конфигурации, которые зависят от одного или нескольких других параметров. например

int Prop1 { get; }
int Prop2 { get; }
int Prop3 { get { return Prop1*Prop2; }

Если ваш язык изначально не поддерживает идиому свойства, возможно, для достижения того же эффекта есть обходной путь, или вы просто создаете класс-оболочку, который предоставляет настройки бонусов. Если вы не можете иным образом использовать преимущества свойств, в противном случае это будет пустой тратой времени на написание вручную и использование методов получения / установки просто для того, чтобы угодить некоему ОО-богу. Вам будет лучше с простым старым полем.

Вам может понадобиться система для объединения и получения нескольких конфигов из разных мест в порядке приоритета. Этот порядок приоритета должен быть четко определен и понятен всем разработчикам / пользователям, например, рассмотреть реестр Windows HKEY_CURRENT_USER / HKEY_LOCAL_MACHINE. Вы должны сделать этот функциональный стиль, чтобы ваши конфиги оставались только для чтения, т.е.

final_conf = merge(user_conf, machine_conf)

скорее, чем:

conf.update(user_conf)

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

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


+1 Спасибо за ответ. Ранее, когда я сохранил пользовательские настройки, я только что прочитал из файла, такого как, .iniчтобы он был удобочитаемым для человека, но вы предлагаете мне сериализовать класс с переменными в?
— Энди

.ini легко разбирать, и есть также много API, которые включают .ini в качестве возможного формата. Я предлагаю вам использовать такие API, чтобы помочь вам выполнить основную работу по синтаксическому анализу текста, но в конечном итоге вы должны инициализировать класс POD. Я использую термин сериализация в общем смысле копирования или чтения в полях класса из какого-либо формата, а не в более конкретном определении сериализации класса непосредственно в / из двоичного файла (как обычно понимается, скажем, для java.io.Serializable). ).
— Бенедикт

Теперь я понимаю немного лучше. То есть, по сути, вы говорите, что у вас есть один класс со всеми полями / настройками, а у другого - установить значения в этом классе из файла? Тогда как мне получить значения из первого класса в других классах?
— Энди

От случая к случаю. Некоторые из ваших классов верхнего уровня могут просто нуждаться в ссылке на весь экземпляр Config, передаваемый в них, если они полагаются на очень много параметров. Другим классам, возможно, нужен только один или два параметра, но нет необходимости связывать их с объектом Config (что делает их еще проще тестировать), просто передайте несколько параметров через ваше приложение. Как упоминалось в моем ответе, если вы строите с помощью тестируемой / DI-ориентированной архитектуры, получить значения там, где они вам нужны, обычно не составит труда. Используйте глобальный доступ на свой страх и риск.
— Бенедикт

Итак, как бы вы предположили, что объект config передается классам, если не нужно задействовать наследование? Через конструктор? Итак, в основном методе программы инициируется класс считывателя, который сохраняет значения в классе Config, затем основной метод передает объект Config или отдельные переменные другим классам?
— Энди

4

В общем (на мой взгляд) лучше всего позволить приложению разобраться с тем, как хранится конфигурация, и передать конфигурацию в ваши модули. Это обеспечивает гибкость в том , как настройки будут сохранены , так что вы можете ориентировать файлы или веб - сервисы или базы данных или ...

Кроме того, на приложение ложится бремя «что происходит, когда что-то не получается», кто лучше знает, что означает этот сбой.

И это значительно упрощает модульное тестирование, когда вы можете просто передать объект конфигурации, а не обращаться к файловой системе или решать проблемы параллелизма, возникающие при статическом доступе.


Спасибо за ваш ответ, но я не совсем понимаю. Я говорю о том, когда пишу приложение, поэтому ничего не нужно иметь дело с конфигурацией, и поэтому, как программист, я знаю, что означает сбой.
— Энди

@andy - конечно, но если модули обращаются к конфигурации напрямую, они не знают, в каком контексте они работают, поэтому они не могут определить, как решать проблемы конфигурации. Если приложение выполняет загрузку, оно может решать любые проблемы, поскольку оно знает контекст. Некоторые приложения могут захотеть
— переключиться

Просто чтобы уточнить здесь, под модулями вы ссылаетесь на классы или на реальные расширения программы? Потому что я спрашиваю, где лучше всего хранить настройки внутри кода в основной программе и как разрешить доступ к настройкам другим классам. Поэтому я хочу загрузить файл конфигурации и затем сохранить настройку где-нибудь / каким-то образом, чтобы мне не приходилось ссылаться на файл.
— Энди

0

Если вы используете .NET для программирования классов, у вас есть разные опции, такие как Resources, web.config или даже пользовательский файл.

Если вы используете Resources или web.config, данные на самом деле хранятся в файле XML, но загрузка происходит быстрее.

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

Для любого другого файла или языка программирования ответ выше Бенедикт будет работать.


Спасибо за ваш ответ и извините за медленный ответ. Использование ресурсов было бы хорошим вариантом, но я только что понял, что это, вероятно, не лучший вариант для меня, поскольку я планирую разработать ту же программу на других языках, поэтому я бы предпочел иметь файл XML или JSON, но Прочитайте его в моем собственном классе, чтобы этот же файл можно было прочитать на других языках программирования.
— Энди
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.