Arvind Kumar GS

Passionate software developer, Linux enthusiast, clean code crusader, DIY advocate, Tech Evangelist

Refactoring using Functional Programming

What

Refactoring is a tool used for making code more readable and
understandable to humans, thereby maintainable. It does not effect the
functionality. In fact it should not effect the functionality.

Why

Refactoring is necessary when you have other people reading your code. For that matter, if you revisit your code after a break, you may also find
it difficult to understand. Refactoring also makes it easier to add new
functionality and for others to utilize it in their code. So it is
important to refactor code.

How

Let us look at the following example. Suppose I need to write a program as follows:
Name: ‘extract-properties’.
Description: Extracts and prints value of properties from file
Input:
  • file (of type — xml | property | json ..) and
  • property names of values to extract
  • dynamic map (replaces the dynamic map key (if) found in the property values, with corresponding map values.)
Now we can split the problem into smaller problems as:
  1. Extract properties from given file (Extract method should support file types like — xml, property, json.)
  2. Replace placeholders with dynamic values
There are three ways to solve the problem.
  • Procedural
  • Object Oriented
  • Functional
Lets evaluate all three.

Procedural

Procedural way of solving the problem,
  • main method reads file content and calls extract method.
  • extract method parses the content based on type, using an if condition that calls the
    specific parse method and extracts the properties and returns these
    properties.
  • main method now call replace method to replace the placeholders with its dynamic values.
Now suppose you need to add another handler, say ‘YAML’ file type. You will need to :
  • Add another if else block in ‘extract’ method.
Advantages of the above approach:
  1. Ease to write when extensions/modification are limited.
What are the disadvantages of the above approach?
  1. This causes a bottleneck on the extract method. In a team of developers, constant updates to a single entity by all the developers will result in merge conflicts.
  2. The extract method will increase in size as extensions are added. This will make it less readable.
  3. Having business logic interspersed with your I/O operations will result in unit test cases that will need actual resource versus a mock.
  4. The resource I/O operations code cannot be reused in other parts of the code as it is tightly coupled to the business logic.

Object Oriented

Let’s have a base class
ResourceHandler
. This defines methods extract () and
replace ()
.
extract()
needs to allow for custom implementation to support future enhancements. While
replace()
needs to be common logic to be used irrespective of the custom extract implementation.
We can model this as follows:
As you see ResourceHandler has methods:
  • handle(). This contains the business logic, which for our purposes can be as simple as:
  • public Map handle() {
      extract();
      replace();
    }
  • protected abstract void extract() - contains logic to extract properties from file
  • private void replace() - contains logic to replace properties from dynamic map
Now the abstract method ‘extract’ needs to be overridden by its child classes:
  • XmlResource
  • JsonResource
  • PropertiesResource
Below is the main class.
Advantages of this approach:
  1. This structure allows to add new extensions without modifying the base ‘handler’ method, that is invoked to extract and replace properties. So child classes cannot corrupt the business logic.
  2. There is no more a bottleneck on the extract method as it was with the
    procedural code. Now each custom extract method has it’s own
    implementation in it’s own class. This will reduce the possible
    merge-conflicts between multiple developers working on different custom extract implementation.
  3. Object ‘ResourceHandler’ is closed for modification while being open for extension. This is one of the pillars of the SOLID
    principle. For example if, ResourceHandler is shared as jar, it can
    still be extended to support additional resource types, but the
    underlying base logic is still restricted.
Disadvantages:
  1. The IO logic to extract properties for given type, is still coupled to the
    business logic, due to the structure of the Base class. As the only
    public method is handle, which calls the extract method and replace
    method. So we cannot use extract in isolation.

Functional

Lets implement functional code using interfaces as follows:
ResourceHandler interface defines a single method ‘extract’ that takes the file and list of properties to extract, and returns the corresponding extracted properties from the input file.
We will utilize the ResourceHandler in the business logic, and inject the
specific implementation in to the class ExtractReplace that encapsulates
the business logic as follows:
class ExtractReplace {
  private Map extracted_properties;

  public ExtractReplace(File file, List properties,Map dynamic, ResourceHandler handler) {
    Map props = handler.extract(file, properties);
    extracted_properties = replace(props, dynamic);
  }

  private Map replace(Map properties, Map dynamic) {...}
  @Override
  public String toString() {
    return extracted_properties.toString();
  }
}
Now the main class will look like this:
The main class creates an object of type ExtractReplace by passing an
implementation of ResourceHandler Interface via the Lambda construct as
seen. This demonstrates the capability to define adhoc anonymous
implementations which do not need to be housed in a type of it’s own,
thereby reducing structural complexity.
We can also follow a more structured approach, where the different flavours of ResourceHandlers are housed in their own types.
This structure allows to use XmlHandler/JsonHandler … objects independently for extracting properties. This is an advantage over Object-oriented approach shown above.
The main class can create objects of specific types of ResourceHandler and pass them to the ExtractReplace class as follows:
You can further decouple the if-else code for instantiating ResourceHandler by having a builder class (Using Builder Pattern).
The enhanced ExtractReplace class with ‘builder’ pattern will look like this:
class ExtractReplace{
  private Map extracted_properties;

  static class Builder {
    private File file;
    private List properties;
    private Map dynamic;
    private ResourceHandler handler;
    static Builder getInstance(File file, List properties, Map dynamic){
      this.file = file;
      this.properties = properties;
      this.dynamic = dynamic;
      if (file.getName().endsWith(".json"))
        this.handler = new JsonHandler();
      else if(file.getName().endsWith(".xml"))
        this.handler = new XmlHandler();
  }
    public ExtractReplace build() {
      new ExtractReplace(this);
    }
  }
  private ExtractReplace(Builder builder) {
    Map props = builder.handler.extract(builder.file, builder.properties);
  }
  private Map replace(Map properties, Map dynamic) { ... }

  @Override
  public String toString(){
    return extracted_properties.toString();
  }
}
More on the builder pattern here.
Advantages:
  1. Over and above the advantages that are provided by Object Oriented approach, this also separates the extraction logic from the business logic. Hence you can reuse this extract method.
  2. You can follow a structured approach by creating different implementation classes of ResourceHandler, which can be resused.
  3. You can also follow a adhoc anonymous approach using Lambda functions. This is useful if you are not reusing the extraction logic else where and want to reduce the structural complexity.

Final Notes

The above functional code is an implementation of Strategy pattern, similar to Collections.sort, where you can pass a custom Comparator
implementation. As you can see the code is extensible, easier to
consume, understand and extend.

Tags

Comments

Topics of interest