Пример использования функционального программирования во избежание дублирования кода
от aNNiMON
Допустим, перед нами стоит задача загрузить конфиг приложения. Есть внутренний конфиг, который хранится в файле app.properties внутри jar-файла, и есть внешний — хранится в пользовательской директории ~/.config/app.conf.
Приложение при запуске читает внешний конфиг. Если какого-то параметра в нём нет, будет браться значение из внутреннего. Если внешнего конфига вообще нет — читается внутренний.
Для чтения внутреннего конфига используется класс ResourceBundle и его метод getString(String key), для внешнего — Properties и метод getProperty(String key, String defaultValue), который как раз поможет установить значение по умолчанию, если оно отсутствует в конфиге.
Можно решить задачу так:
Код плох тем, что здесь много повторов, а значит нетрудно допустить ошибку или что-то пропустить.
Давайте взглянем на эти две строчки:
config.setLocale(props.getProperty("locale", res.getString("locale")));
config.setLocale(res.getString("locale"));
Отличия лишь в аргументе функции setLocale. То есть, если сделать нечто такое:
config.setLocale(loader.load("locale"));
и создать две реализации объекта loader, то дублирования получится избежать.
Чтобы это сделать, давайте введём функциональный интерфейс.
Аннотация @FunctionalInterface проверяет на этапе компиляции, что в интерфейсе только один метод, в этом и суть функционального интерфейса.
Теперь мы можем создать две реализации этого интерфейса при помощи лямбда-выражений:
Следующий шаг — выносим заполнение параметров в новый метод:
А вызываем так:
Полный код:
Приложение при запуске читает внешний конфиг. Если какого-то параметра в нём нет, будет браться значение из внутреннего. Если внешнего конфига вообще нет — читается внутренний.
Для чтения внутреннего конфига используется класс ResourceBundle и его метод getString(String key), для внешнего — Properties и метод getProperty(String key, String defaultValue), который как раз поможет установить значение по умолчанию, если оно отсутствует в конфиге.
Можно решить задачу так:
- final Config config = new Config(); // сюда будем собирать параметры
- final ResourceBundle res = ResourceBundle.getBundle(resourceName); // внутренний конфиг
- final Path externalPath = Paths.get(System.getProperty("user.home"), ".config", "app.conf"); // внешний конфиг
- try (InputStream extConfig = Files.newInputStream(externalPath)) {
- // Загружаем внешний конфиг
- final Properties props = new Properties();
- props.load(extConfig);
- config.setLocale(props.getProperty("locale", res.getString("locale")));
- config.setToken(props.getProperty("token", res.getString("token")));
- config.setMaxConnections(Integer.parseInt( props.getProperty("max-connections", res.getString("max-connection")) ));
- } catch (IOException ex) {
- // Заполняем настройки из внутреннего конфига, раз внешний не удалось загрузить
- config.setLocale(res.getString("locale"));
- config.setToken(res.getString("token"));
- config.setMaxConnections(Integer.parseInt( res.getString("max-connection") ));
- }
Давайте взглянем на эти две строчки:
config.setLocale(props.getProperty("locale", res.getString("locale")));
config.setLocale(res.getString("locale"));
Отличия лишь в аргументе функции setLocale. То есть, если сделать нечто такое:
config.setLocale(loader.load("locale"));
и создать две реализации объекта loader, то дублирования получится избежать.
Чтобы это сделать, давайте введём функциональный интерфейс.
- @FunctionalInterface
- interface PropertyLoader {
- String load(String key);
- }
Теперь мы можем создать две реализации этого интерфейса при помощи лямбда-выражений:
- PropertyLoader loader1 = key -> props.getProperty(key, res.getString(key));
- PropertyLoader loader2 = key -> res.getString(key);
- // или короче, через ссылку на метод:
- PropertyLoader loader3 = res::getString;
Следующий шаг — выносим заполнение параметров в новый метод:
- private void loadConfig(Config config, PropertyLoader loader) {
- config.setLocale(loader.load("locale"));
- config.setToken(loader.load("token"));
- config.setMaxConnections(Integer.parseInt( loader.load("max-connections") ));
- }
А вызываем так:
- final Config config = new Config();
- final ResourceBundle res = ResourceBundle.getBundle(resourceName);
- final Path externalPath = Paths.get(System.getProperty("user.home"), ".config", "app.conf");
- try (InputStream extConfig = Files.newInputStream(externalPath)) {
- final Properties props = new Properties();
- props.load(extConfig);
- loadConfig(config, key -> props.getProperty(key, res.getString(key)));
- } catch (IOException ex) {
- loadConfig(config, res::getString);
- }
Полный код: