JavaRush /Java блог /Random UA /Константи та інтернаціоналізація
Joysi
41 рівень

Константи та інтернаціоналізація

Стаття з групи Random UA
Під час роботи програма спирається на обчисленнях чи вводе-выводе використовується як змінні, а й явно прописані константи. Допустимо Ви обробляєте фінансові дані і по ходу коду кілька разів виводьте результати (нехай у консоль) приблизно таким чином: Константи та інтернаціоналізація - 1
System.out.println("Разомвое количество =" + countLoans);
...
String strCount = "Разомвое количество :" + loans.calc();
...
System.out.println(strCount + strSumDesc);
І це дуже погано з кількох причин.
  1. Порушення принципу DRY (Don't repeat yourself - не повторюй).
    Дублювання тексту та, як наслідок, збільшення розмірів скомпільованого коду. Також при цьому можливі описки.

  2. Порушення принципу KISS (keep it simple stupid – робіть речі простіше).
    У разі потреби зміни такого роду констант доведеться прошерстити весь код.

  3. Кожна зміна константи вимагатиме перекомпіляції коду.

  4. Введення мультимовності вимагатиме (за такої побудови коду) багато додаткових конструкцій до кожної такої константи.

Перші дві причини усуне введення констант з модифікаторами static finalта їх ініціалізацією + застосування по ходу коду. Тобто. створюємо:
public class Constants {
    public static final int COUNT_DEPARTMENTS = 20;
    public static final String MSG_TOTAL_AMOUNT = "Разомвое количество";
    ...
}
і застосовуємо приблизно як System.out.println(Constants.MSG_TOTAL_AMOUNTCOUNT_DEPARTMENT + "= " + countLoans);Якщо нам необхідно змінити фразу, ми робимо тільки в одному місці . Щоб усунути третю причину можна скористатися класом Properties, більш докладно на відповідних лекціях JavaRush і деяких big-завданнях другої половини 20-х рівнів. Найчастіше вживані змінні можна винести як окремі public static final, що рідше використовуються. Отримувати через виклик методу PROPS.getProperty().
public class Constants {
    public static final int COUNT_DEPARTMENTS;
    public static final String MSG_TOTAL_AMOUNT;
    public static final Properties PROPS;

    static {
        Logger log = LogManager.getLogger(Constants.class);
        PROPS = new Properties();
        try{
            fis = new FileInputStream("confs/config.properties");
            PROPS.load(fis);
            fis.close();
        } catch ( IOException e) {
            log.error(e.getMessage());
        }
        MSG_TOTAL_AMOUNT = PROPS.getProperty("msg.total.amount");
        COUNT_DEPARTMENTS = Integer.parseInt(PROPS.getProperty("count.departments"));
        ...
    }
}
Залишилося розібратися з інтернаціоналізацією. Зробимо так, щоб у staticблоці відбувалося завантаження потрібного файлу properties. Для цього працюватимемо з ResourceBundle. Зазначимо, що із ResourceBundleстандартними методами можна зчитувати значення ключів, але не встановлювати їх. Тому, як і раніше, зберігаємо в окремому Properties (config.properites)файлі параметри, що не мають відношення до інтернаціоналізації (до того ж деякі з них можна змінювати в процесі роботи програми). Наприклад, мова інтерфейсу. Зручний інтерфейс IDEA допоможе нам. Створюємо його: Константи та інтернаціоналізація - 2Додаємо потрібні мови: Константи та інтернаціоналізація - 3У структурі проекту IDEA відображається як: Константи та інтернаціоналізація - 3Наповнюємо вмістом: Константи та інтернаціоналізація - 5
public class Constants {
    public static final int COUNT_DEPARTMENTS;
    public static final String MSG_TOTAL_AMOUNT;
    public static final Properties PROPS;
    public static final ResourceBundle UI_LANGUAGE;

    static {
        Logger log = LogManager.getLogger(Constants.class);
        PROPS = new Properties();
        try{
            fis = new FileInputStream("confs/config.properties");
            PROPS.load(fis);
            fis.close();
        } catch ( IOException e) {
            log.error(e.getMessage());
        }
        if (PROPS.getProperty("ui.language").equalsIgnoreCase("RUS"))
            UI_LANGUAGE = ResourceBundle.getBundle("messages", CharsetControl.RUS);
        else
            UI_LANGUAGE = ResourceBundle.getBundle("messages", CharsetControl.ENG);

        MSG_TOTAL_AMOUNT = UI_LANGUAGE.getString("msg.total.amount");
        COUNT_DEPARTMENTS = Integer.parseInt(PROPS.getProperty("count.departments"));
        ...
    }
}
Ми домоглися, що мова інтерфейсу змінюється без втручання у код . До того ж ми отримали чіткий поділ праці : можемо віддати російський propertiesфайл перекладачеві-гуманітарію, який у зручному для себе текстовому редакторі його переведе. Нам залишиться лише перейменувати його в потрібне ім'я файлу та розмістити назад у ResourceBundle. Невелике доповнення (використання CharsetControl): Properties файли не чутливі до кодування (обробляють вміст файлу виключно по одному байту, що дорівнює використання ISO8859-1) і кирабоця в Properties-файлу буде відображена некоректно. Щоб уникнути цього, можна:
  • Переганяти кирабочні символи в ESCAPE-послідовності \uxxxx через native2ascii

  • Допомогти Propertiesпередавши йому необхідний ResourceBundle.Control, у якому нам необхідно перевантажити метод (код знайшов на просторах інтернету + трохи модифікував).

Можливо комусь допоможе:
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.*;
import java.util.*;

public class CharsetControl extends ResourceBundle.Control {

    public static final CharsetControl UTF_8 = new CharsetControl("utf8");
    public static final CharsetControl RUS = new CharsetControl("cp1251");
    public static final Locale    ENG = new Locale("en","GB");

    private Charset charset;

    public CharsetControl(String charset) {
        this(Charset.forName(charset));
    }

    public CharsetControl(Charset charset) {
        this.charset = charset;
    }

    public Charset getCharset() {
        return charset;
    }

    @Override
    public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
            throws IllegalAccessException, InstantiationException, IOException {

        String bundleName = toBundleName(baseName, locale);
        ResourceBundle bundle = null;
        if (format.equals("java.class")) {
            try {
                Class<? extends ResourceBundle> bundleClass  =
                        (Class<? extends ResourceBundle>)loader.loadClass(bundleName);

                if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
                    bundle = bundleClass.newInstance();
                } else {
                    throw new ClassCastException(bundleClass.getName()
                            + " cannot be cast to ResourceBundle");
                }
            } catch (ClassNotFoundException e) {
            }
        } else if (format.equals("java.properties")) {
            final String resourceName = toResourceName(bundleName, "properties");
            final ClassLoader classLoader = loader;
            final boolean reloadFlag = reload;
            InputStream stream = null;
            try {
                stream = AccessController.doPrivileged(
                        new PrivilegedExceptionAction<InputStream>() {
                            public InputStream run() throws IOException {
                                InputStream is = null;
                                if (reloadFlag) {
                                    URL url = classLoader.getResource(resourceName);
                                    if (url != null) {
                                        URLConnection connection = url.openConnection();
                                        if (connection != null) {
                                            connection.setUseCaches(false);
                                            is = connection.getInputStream();
                                        }
                                    }
                                } else {
                                    is = classLoader.getResourceAsStream(resourceName);
                                }
                                return is;
                            }
                        });
            } catch (PrivilegedActionException e) {
                throw (IOException) e.getException();
            }
            if (stream != null) {
                try {
                    bundle = new PropertyResourceBundle(new InputStreamReader(stream, getCharset()));
                } finally {
                    stream.close();
                }
            }
        } else {
            throw new IllegalArgumentException("unknown format: " + format);
        }
        return bundle;
    }
}
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ