Найкращий спосіб завантажити налаштування програми


24

Простий спосіб збереження налаштувань програми Java представлений текстовим файлом з розширенням ".properties", що містить ідентифікатор кожного параметра, пов'язаного з певним значенням (це значення може бути числом, рядком, датою тощо). . C # використовує аналогічний підхід, але текстовий файл повинен бути названий "App.config". В обох випадках у вихідному коді потрібно ініціалізувати певний клас для налаштувань читання: у цього класу є метод, який повертає значення (як рядок), пов’язане із зазначеним ідентифікатором налаштування.

// Java example
Properties config = new Properties();
config.load(...);
String valueStr = config.getProperty("listening-port");
// ...

// C# example
NameValueCollection setting = ConfigurationManager.AppSettings;
string valueStr = setting["listening-port"];
// ...

В обох випадках слід проаналізувати рядки, завантажені з файлу конфігурації, і призначити перетворені значення відповідним типізованим об'єктам (помилки розбору можуть виникнути під час цієї фази). Після кроку розбору ми повинні перевірити, що значення параметрів належать певній області дійсності: наприклад, максимальний розмір черги повинен бути позитивним значенням, деякі значення можуть бути пов'язані (наприклад: min <max ), і так далі.

Припустимо, що програма повинна завантажити налаштування, як тільки вона починається: іншими словами, перша операція, яку виконує програма, - це завантаження налаштувань. Будь-які недійсні значення параметрів повинні автоматично замінюватися значеннями за замовчуванням: якщо це трапляється з групою пов’язаних налаштувань, ці налаштування встановлюються за замовчуванням.

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

Для того, щоб вирішити цю проблему, я подумав про використання шаблону методу шаблонів таким чином.

public abstract class Setting
{
    protected abstract bool TryParseValues();

    protected abstract bool CheckValues();

    public abstract void SetDefaultValues();

    /// <summary>
    /// Template Method
    /// </summary>
    public bool TrySetValuesOrDefault()
    {
        if (!TryParseValues() || !CheckValues())
        {
            // parsing error or domain error
            SetDefaultValues();
            return false;
        }
        return true;
    }
}

public class RangeSetting : Setting
{
    private string minStr, maxStr;
    private byte min, max;

    public RangeSetting(string minStr, maxStr)
    {
        this.minStr = minStr;
        this.maxStr = maxStr;
    }

    protected override bool TryParseValues()
    {
        return (byte.TryParse(minStr, out min)
            && byte.TryParse(maxStr, out max));
    }

    protected override bool CheckValues()
    {
        return (0 < min && min < max);
    }

    public override void SetDefaultValues()
    {
        min = 5;
        max = 10;
    }
}

Проблема полягає в тому, що таким чином нам потрібно створити новий клас для кожного параметра, навіть для одного значення. Чи є інші варіанти вирішення подібної проблеми?

Підсумовуючи:

  1. Легке обслуговування: наприклад, додавання одного або декількох параметрів.
  2. Розширюваність: перша версія програми може прочитати один файл конфігурації, але пізніші версії можуть дати можливість налаштування для багатьох користувачів (адміністратор встановлює базову конфігурацію, користувачі можуть встановлювати лише певні налаштування тощо).
  3. Об'єктно-орієнтований дизайн.

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

Відповіді:


8

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

Кінцевий результат надійний і насамперед простий в управлінні.


7

Розглянемо це з двох точок зору: API для отримання значень конфігурації та формату зберігання. Вони часто пов'язані, але корисно розглянути їх окремо.

API конфігурації

Шаблон методу шаблонів дуже загальний, але я сумніваюся, чи справді вам потрібна така загальність. Вам знадобиться клас для кожного типу значень конфігурації. У вас насправді так багато типів? Я здогадуюсь, що вам вдасться обійтись лише кількома елементами: струнними, int, floats, booleans та enums. З огляду на це, у вас може бути Configклас, який містить декілька методів:

int getInt(name, default, min, max)
float getFloat(name, default, min, max)
boolean getBoolean(name, default)
String getString(name, default)
<T extends Enum<T>> T getEnum(name, Class<T> enumClass, T default)

(Я думаю, що я отримав загальну інформацію про останнє право.)

В основному кожен метод знає, як обробити розбір значення рядка з конфігураційного файла та обробити помилки та повернути значення за замовчуванням, якщо це доречно. Перевірка діапазону чисельних значень, ймовірно, достатня. Можливо, вам доведеться мати перевантаження, які опускають значення діапазону, що було б еквівалентно наданню діапазону Integer.MIN_VALUE, Integer.MAX_VALUE. Enum - безпечний для типу спосіб перевірки рядка щодо фіксованого набору рядків.

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

Формат зберігання

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

Теластин згадував серіалізовані об’єкти. Це можливість, хоча серіалізація має свої труднощі. Це двійкові, а не текстові, тому важко бачити та редагувати значення. Ви повинні мати справу з сумісністю серіалізації. Якщо значень відсутнє у серіалізованого вводу (наприклад, ви додали поле до класу Config і читаєте стару серіалізовану його форму), нові поля ініціалізуються на null / zero. Ви повинні написати логіку, щоб визначити, чи потрібно заповнювати якесь інше значення за замовчуванням. Але чи вказує нуль відсутність значення конфігурації, чи було вказано нуль? Тепер вам доведеться налагодити цю логіку. Нарешті (не впевнений, що це викликає занепокоєння), вам все ж може знадобитися перевірити значення в потоці серіалізованих об'єктів. Зловмисному користувачеві (хоча і незручно) можна непомітно змінювати серіалізований потік об’єктів.

Я б сказав, щоб дотримуватися властивостей, якщо це взагалі можливо.


2
Гей Стюарт, приємно бачити вас тут :-). Я додам відповіді Stuarts про те, що я думаю, що ваша ідея tempalte буде працювати на Java, якщо ви використовуєте Generics для сильного введення, тож у вас також може бути опція Setting <T>.
Мартійн Вербург

@StuartMarks: Ну, моя перша думка була просто написати Configклас і використовувати підхід , запропонований вами: getInt(), getByte(), getBoolean()і т.д .. Продовжуючи цю ідею, я спочатку прочитав всі значення , і я міг зв'язати кожне значення прапора (цей прапор є помилковим, якщо під час десеріалізації сталася проблема, наприклад, помилки розбору). Після цього я міг розпочати фазу перевірки всіх завантажених значень і встановити будь-які значення за замовчуванням.
enzom83

2
Я б віддав перевагу якомусь підходу JAXB або YAML для спрощення всіх деталей.
Гері Роу

4

Як я це зробив:

Ініціалізуйте все до значень за замовчуванням.

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


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

2

Чи є інші варіанти вирішення подібної проблеми?

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


1
Ніякого роздуму над розбором чи перетворенням, без накручування конфігураційних рядків, без сміття. Що ви маєте на увазі?
enzom83

1
Я маю на увазі, що: 1. Вам не потрібно брати результат AppConfig (рядок) і аналізувати його на потрібне. 2. Вам не потрібно вказувати будь-який тип рядка, щоб вибрати потрібний параметр config; це одна з тих речей, яка схильна до людських помилок і є важкою для рефактора, і 3. вам не потрібно робити перетворення інших типів, коли збираєтесь задавати значення програмно.
Теластин

2

Принаймні, у .NET, ви можете досить легко створити власні сильно типізовані об’єкти конфігурації - див. Цю статтю MSDN для швидкого прикладу.

Підказка: загорніть ваш конфігураційний клас в інтерфейс і нехай ваша програма спілкується з цим. Легко вводити підроблену конфігурацію для тестів або для отримання прибутку.


Я прочитав статтю MSDN: це цікаво, по суті кожен підклас ConfigurationElementкласу міг представляти групу значень, а для будь-якого значення можна вказати валідатор. Але якщо, наприклад, я хотів представити конфігураційний елемент, який складається з чотирьох ймовірностей, чотири значення ймовірності співвідносяться, оскільки їх сума повинна дорівнювати 1. Як я підтверджую цей елемент конфігурації?
enzom83

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