Читая о функциональном программировании, я видел, как многие люди рекомендуют избавиться от операторов if/else (см. Здесь , здесь и там ). Основная идея заключается в том, что удаление if/else (или условных операторов) облегчает отслеживание и понимание вашего кода. Удаление условных операторов заставит вас мыслить декларативно. Многие функциональные программисты согласны с тем, что декларативное программирование обычно легче понять, чем императивное программирование .
Итак, как мы это делаем на Java? В своей последней статье я дал краткий обзор функциональных интерфейсов Java и того, как вы можете использовать их, чтобы сделать свой код более декларативным. Давайте выясним, как мы можем использовать их, чтобы помочь устранить некоторую логику if/else.
Я столкнулся с чем-то похожим на приведенный ниже код в проекте, над которым я работал. В коде есть метод, который определяет, какой тип отчета создавать. Генерация отчета основана на том, какой формат передается в метод. Не зацикливайтесь на коде ответа. Для этого упражнения вы можете заменить логику ответа любым типом алгоритма, который может отличаться в зависимости от решения во время выполнения. Обратите внимание, что каждый случай делает что-то другое (или имеет другую “стратегию”) с ответом.
// in controller
public void formatResponse(String format, HttpServletResponse response) {
if("csv".equals(format)) {
response.setContentType("text/csv");
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.csv;");
} else if ("html".equals(format))
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.html;");
} else if ("pdf".equals(format)) {
response.setContentType(MediaType.APPLICATION_PDF_VALUE);
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.pdf;");
} else {
response.setContentType(MediaType.TEXT_XML);
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline:filename=myReport.xml;");
}
}
Этот метод был вызван в контроллере как таковой:
public void getReport(@PathVariable String reportName, @RequestBody String format, HttpServletResponse response) {
// other code removed for brevity
formatResponse(format, response);
// other code removed for brevity
}
Во-первых, позвольте мне сказать, что для рефакторинга операторов if/else можно использовать множество шаблонов и методов. Это включает (но не ограничивается) Шаблон команды, Шаблон фабрики, Перечисления и карты, а также Шаблон Стратегии. В следующем примере использования я решил реализовать шаблон стратегии.
Типичный шаблон стратегии предполагает, что мы создаем интерфейс, а затем создаем классы, реализующие интерфейс. Может быть, что-то вроде этого:
public interface ResponseStrategy {
void accept(HttpServletResponse response);
}
public static class CsvStrategy implements ResponseStrategy {
@Override
public void accept(HttpServletResponse response){
response.setContentType("text/csv");
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.csv;");
}
}
public static class HtmlStrategy implements ResponseStrategy {
@Override
public void accept(HttpServletResponse response){
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.html;");
}
}
// other classes ect...
В таком подходе нет ничего плохого. Но внедрение нового класса для каждой стратегии может стать громоздким. Итак, как функциональные интерфейсы могут помочь нам здесь?
Как мы можем использовать функциональные интерфейсы в Шаблоне Стратегии?
Функциональные интерфейсы могут быть реализованы с помощью лямбда-выражений . Один из способов думать об этом заключается в следующем. Иногда простые классы могут быть заменены анонимными классами. А анонимные классы, у которых есть только один метод, могут быть заменены лямбдой. В нашем вышеприведенном случае на этих занятиях происходит не так уж много интересного. И каждый класс реализует только один метод. Звучит как хороший кандидат для лямбда-выражений.
Мы могли бы создать ваш собственный функциональный интерфейс.
@FunctionalInterface
public interface ResponseStrategy {
void accept(HttpServletResponse response);
}
Или мы могли бы использовать встроенный функциональный интерфейс Java Util Потребитель . В котором говорится: “[Потребитель] представляет операцию, которая принимает один входной аргумент и не возвращает результата. В отличие от большинства других функциональных интерфейсов, ожидается, что потребитель будет работать с помощью побочных эффектов “. Поскольку при использовании Consumer мне пришлось бы написать на один интерфейс меньше, я буду использовать его следующим образом:
private static ConsumercsvStrategy = response -> { response.setContentType("text/csv"); response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.csv;"); } private static Consumer htmlStrategy = response -> { response.setContentType(MediaType.TEXT_HTML_VALUE); response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.html;"); } private static Consumer pdfStrategy = response -> { response.setContentType(MediaType.APPLICATION_PDF_VALUE); response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.pdf;"); } private static Consumer xmlStrategy = response -> { response.setContentType(MediaType.TEXT_XML); response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.xml;"); }
Примечание: вам не нужно передавать лямбды в переменные; при желании вы можете передать всю функцию целиком. Я сделал это для удобства чтения.
Теперь у нас есть 4 лямбда-выражения, которые мы можем передать в карту, ключ которой соответствует требуемому формату.
private Map> createStrategies() { Map > strategies = new HashMap<>(); strategies.put("csv", csvStrategy); strategies.put("html", htmlStrategy); strategies.put("pdf", pdfStrategy); strategies.put("xml", xmlStrategy); return Collection.unmodifiableMap(strategies); }
Поскольку блок “еще” в старом коде используется по умолчанию, мы можем использовать карту следующим образом:
public void formatResponse(String format, HttpServletResponse response) {
Map> strategies = createStrategies();
strategies.getOrDefault(format, xmlStrategy).accept(response);
}
Вместо того, чтобы проверять условие и затем выполнять что-либо, код захватывает потребителя с карты и вызывает функцию потребителя с переданным ответом. Больше никакой логики “если/иначе”! Это простой пример использования функциональных интерфейсов Java, которые помогут вам писать более чистый и лаконичный код. Добавьте комментарий, если вы использовали что-либо подобное, чтобы избавиться от операторов if/else, или если у вас есть какие-либо другие интересные способы использования функциональных интерфейсов.
Оригинал: “https://dev.to/phouchens/functional-programming-in-java-refactoring-if-else-logic-with-the-help-of-functional-interfaces-2hcj”