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