Де завантажити та зберігати налаштування з файлу?


9

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

  • Якщо програма мала простий settings.iniфайл, чи слід його вміст завантажувати у load()методі класу чи, можливо, у конструкторі?
  • Чи варто зберігати значення у public staticзмінних, чи повинні існувати staticметоди отримання та встановлення властивостей?
  • Що має відбутися у випадку, якщо файл не існує чи не читається? Як би ви дозволили іншій програмі знати, що вона не може отримати ці властивості?
  • тощо.

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


1
Для .NET найкраще скористатися App.config та класом System.Configuration.ConfigurationManager, щоб отримати налаштування
Gibson

@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

Це насправді дуже важливе питання, і це часто робиться неправильно, оскільки йому не надається достатнього значення, хоча він є основною частиною майже кожної програми. Ось мої вказівки:

Ваш клас конфігурацій, який містить усі налаштування, повинен бути просто простим старим типом даних, структура / клас:

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

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

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

Ви не повинні лінуватися вводити всі налаштування як поля, просто зробивши це:

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

Це заманливо, оскільки це означає, що ви можете написати узагальнений клас серіалізації, якому не потрібно знати, з якими полями він має справу, але це неправильно, і я поясню, чому за мить.

Серіалізація конфігурації проводиться в абсолютно окремому класі. Який би API чи бібліотеку ви не використовували для цього, тіло функції вашої серіалізації має містити записи, які в основному складають як мапу від шляху / ключа у файлі до поля на об’єкті. Деякі мови забезпечують хорошу самоаналіз і можуть зробити це для вас поза полем, інші вам доведеться чітко записати відображення, але головне, що вам доведеться написати карти лише один раз. Наприклад, розгляньте цей витяг, який я адаптував із документації для аналізу параметрів програми c ++ програми розширення:

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, або не існує. Тобто збій повинен виникнути під час читання файлу, оскільки проблема полягає у форматі / валідації файлу, і вам слід викинути код виключення / повернення та повідомити про точну проблему. Не варто зволікати з цим пізніше в програмі. Ось чому вам не слід спокушатись зафіксувати весь конфліктологічний стиль словника, як згадувалося вище, який не вийде з ладу під час зчитування файлу - оскільки кастинг затримується до необхідності значення.

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

В ідеалі ви читаєте у файлі в одному місці вашої програми, тобто у вас є лише один екземпляр " ConfigReader". Однак, якщо ви боретеся з тим, щоб екземпляр Config передався туди, куди вам це потрібно, краще мати другий ConfigReader, ніж це ввести глобальний конфігурацію (що, мабуть, означає, що ОП означає "статичний" "), що приводить мене до наступного моменту:

Уникайте спокусливої ​​пісні сирени сингла: "Я врятую вас, щоб пройти цей клас класу навколо, всі ваші конструктори будуть прекрасними та чистими. Далі, це буде так просто". Правда з добре розробленою тестованою архітектурою вам навряд чи потрібно буде пройти клас Config або його частини вниз через ці багато класів вашої програми. Що ви знайдете, у своєму класі вищого рівня, вашій головній () функції чи будь-що інше, ви розплутаєте конф на індивідуальні значення, які ви надасте класам компонентів як аргументи, які ви потім зведете разом (залежність вручну ін’єкція). Конфігурація одиночної / глобальної / статичної роботи значно ускладнить реалізацію та розуміння вашої програми - наприклад, вона заплутає нових розробників у вашій команді, які не знають, що вони повинні встановити глобальний стан для тестування матеріалів.

Якщо ваша мова підтримує властивості, ви повинні використовувати їх для цієї мети. Причина полягає в тому, що додати «похідні» налаштування конфігурації, які залежать від одного або декількох інших параметрів, буде дуже просто. напр

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

Якщо ваша мова не підтримує ідіому властивості, вона може мати шлях для досягнення такого ж ефекту, або ви просто створите клас обгортки, який забезпечує налаштування бонусу. Якщо ви не можете надати перевагу властивостям, інакше марно витрачати час вручну і користуватися геттерами / сеттерами просто для того, щоб порадувати якогось бога OO. Вам буде краще з простим старим полем.

Можливо, вам знадобиться система для об'єднання та прийому декількох конфігурацій з різних місць у порядку черговості. Цей порядок пріоритетності повинен бути чітко визначений і зрозумілий всіма розробниками / користувачем, наприклад, врахуйте реєстр 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. Я використовую термін serialise в загальному розумінні копіювання або читання в полях класу з якогось формату, а не більш конкретного визначення серіалізації класу безпосередньо в / з двійкового (як це зазвичай розуміється, наприклад, java.io.Serializable ).
Бенедикт

Тепер я розумію трохи краще. Отже, по суті, ви говорите про наявність одного класу з усіма полями та налаштуваннями, а іншого, щоб встановити значення цього класу з файлу? Потім, як я можу отримати значення з першого класу в інших класах?
Енді

В кожному конкретному випадку. Для деяких класів вищого рівня може знадобитися посилання на весь екземпляр Config, що передається в них, якщо вони покладаються на стільки параметрів. Іншим класам, можливо, потрібен один або два параметри, але немає необхідності з'єднувати їх до об'єкта Config (що ще полегшує їх тестування), просто передайте кілька параметрів (-ів) вниз через вашу програму. Як було сказано у моїй відповіді, якщо ви будуєте архітектуру, орієнтовану на тест / DI, отримання значень там, де вони вам потрібні, як правило, не складе труднощів. Скористайтеся глобальним доступом за своєю небезпекою.
Бенедикт

Отже, як би ви запропонували об’єкт config передати класам, якщо в спадкування не потрібно залучати? Через конструктор? Отже, в основному методі програми ініціюється клас зчитування, який зберігає значення в класі Config, потім основний метод передає об'єкт Config або окремі змінні іншим класам?
Енді

4

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

Крім того, на додаток покладено тягар "того, що відбувається, коли справи провалюються", хто найкраще знає, що означає цей збій.

І це полегшує тестування одиниць, коли ви можете просто пройти об’єкт конфігурації, а не доторкатися до файлової системи або вирішувати проблеми одночасності, введені статичним доступом.


Дякую за вашу відповідь, але я не дуже його розумію. Я говорю про те, коли пишуть додаток, тому з конфігурацією немає нічого, і, як програміст, я знаю, що означає збій.
Енді

@andy - звичайно, але якщо модулі отримують доступ до конфігурації безпосередньо, вони не знають, в якому контексті вони працюють, тому не можуть визначити, як вирішити проблеми з конфігурацією. Якщо програма виконує завантаження, вона може вирішувати будь-які проблеми, оскільки знає контекст. Деякі додатки можуть захотіти перейти на значення за замовчуванням, інші хочуть перервати і т.д.
Telastyn

Просто для уточнення тут, за модулями ви посилаєтесь на класи чи фактичні розширення до програми? Оскільки я запитую про те, де найкраще зберігати налаштування насправді всередині коду до основної програми та як дозволити іншим класам доступ до налаштувань. Тому я хочу завантажити файл конфігурації, а потім зберегти налаштування десь / якось, щоб мені не довелося продовжувати посилатися на файл.
Енді

0

Якщо ви використовуєте .NET для програмування класів, у вас є різні параметри, такі як Resources, web.config або навіть власний файл.

Якщо ви використовуєте Resources або web.config, дані фактично зберігаються у файлі XML, але завантаження відбувається швидше.

Отримання даних з цих файлів та зберігання їх в іншому місці буде подібним до подвійного використання пам'яті, оскільки вони завантажуються в пам'ять за замовчуванням.

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


Дякую за вашу відповідь і вибачте за повільну відповідь. Використання ресурсів було б хорошим варіантом, але я щойно зрозумів, що це, мабуть, не найкращий варіант для мене, коли я планую розробляти ту саму програму на інших мовах, тому я вважаю за краще мати файл XML або JSON, але читати його в моєму власному класі, щоб той самий файл можна було прочитати в інших мовах програмування.
Енді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.