DEV Community

Cover image for Functional Programming in Java? Refactoring if/else logic with the help of Functional Interfaces.
Perry H
Perry H

Posted on

Functional Programming in Java? Refactoring if/else logic with the help of Functional Interfaces.

When reading about Functional Programming, I have seen many folks recommend getting rid of if/else statements (See here, here, and here). The main idea is removing if/else (or conditional statments) makes your code easier to follow and understand. Removing conditional statements will force you to think in a declarative way. Many functional programmers agree that declarative programming is generally easier to understand than imperative programming.

So how do we do this in Java? In my last article, I gave a brief overview of Java's Functional Interfaces and how you can use them to make your code more declarative. Let's find out how we can use them to help remove some if/else logic.

I ran into something similar to the code below on a project I was working on. The code has a method that determines which kind of report to generate. Report generation is based on what format is passed into the method. Don't get to focused on the response code. For this exercise, you could replace the response logic with any type of algorithm that could be different based on a runtime decision. Notice that each case does something different (or has a different "strategy") with the response.

// 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;");
  }
}
Enter fullscreen mode Exit fullscreen mode

This method was called in the controller as such:

public void getReport(@PathVariable String reportName, @RequestBody String format, HttpServletResponse response) {
// other code removed for brevity
  formatResponse(format, response);
// other code removed for brevity
}
Enter fullscreen mode Exit fullscreen mode

First, let me say that multiple patterns and techniques can be used to refactor if/else statements. This includes (but is not limited to) the Command Pattern, the Factory Pattern, Enums and Maps, and the Strategy Pattern. In the following use case, I decided on implementing the Strategy Pattern.

A typical strategy pattern would have us create an Interface and then create classes that implement the Interface. Maybe something like this:

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...
Enter fullscreen mode Exit fullscreen mode

Nothing wrong with that approach. But implementing a new class for every strategy can become cumbersome. So how can Functional Interfaces help us out here?

How can we use Functional Interfaces in the Strategy Pattern?

Functional Interfaces can be implemented by lambda expressions. One way to think of it is as follows. Sometimes, simple classes can be replaced by anonymous classes. And anonymous classes that only have one method can be replaced by a lambda. In our above case, we don't have too much going on in those classes. And each class only implements one method. Sounds like a good candidate for lambda expressions.

We could make our own functional interface.

@FunctionalInterface
public interface ResponseStrategy {
  void accept(HttpServletResponse response);
}
Enter fullscreen mode Exit fullscreen mode

Or, we could use Java Util's built-in functional interface Consumer. Which states: "[A Consumer] represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects."
Since using Consumer would be one less interface I would have to write, I will use it as follows:

private static Consumer<HttpServletResponse> csvStrategy = response -> {
    response.setContentType("text/csv");
    response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.csv;");
} 
private static Consumer<HttpServletResponse> htmlStrategy = response -> {
    response.setContentType(MediaType.TEXT_HTML_VALUE);
    response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.html;");
}
private static Consumer<HttpServletResponse> pdfStrategy = response -> {
    response.setContentType(MediaType.APPLICATION_PDF_VALUE);
    response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.pdf;");
}
private static Consumer<HttpServletResponse> xmlStrategy = response -> {
    response.setContentType(MediaType.TEXT_XML);
    response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=myReport.xml;");
}
Enter fullscreen mode Exit fullscreen mode

Note: you do not have to pass the lambda's into variables; you can pass the entire function if desired. I did so for readability.

Now we have 4 lambda expressions that we can pass into a map whose key is the required format.

private Map<String, Consumer<HttpServletResponse>> createStrategies() {
  Map<String, Consumer<HttpServletResponse>> strategies = new HashMap<>(); 
  strategies.put("csv", csvStrategy);
  strategies.put("html", htmlStrategy);
  strategies.put("pdf", pdfStrategy);
  strategies.put("xml", xmlStrategy);
  return Collection.unmodifiableMap(strategies);
}
Enter fullscreen mode Exit fullscreen mode

Since the "else" block in the old code is the default case, we can use a map as follows:


public void formatResponse(String format, HttpServletResponse response) {
   Map<String, Consumer<HttpServletResponse>> strategies = createStrategies();
   strategies.getOrDefault(format, xmlStrategy).accept(response);
}

Enter fullscreen mode Exit fullscreen mode

Instead of checking a condition and then executing something, the code grabs a Consumer from the map and calls the Consumer's function with the passed-in response. No more if/else logic!
This is a simple example of using Java's Functional Interfaces to help you write cleaner, more concise code. Add a comment if you have utilized anything like this to get rid of if/else statements, or if you have any other cool ways to use Functional Interfaces.

Discussion (0)