Рубрики
Без рубрики

Функциональное программирование на Java? Рефакторинг логики if/else с помощью функциональных интерфейсов.

Читая о функциональном программировании, я видел, как многие люди рекомендуют избавиться от if /else… Помеченный как java, рефакторинг, функциональный.

Читая о функциональном программировании, я видел, как многие люди рекомендуют избавиться от операторов 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 Consumer csvStrategy = 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”