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.
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.
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:
Now we can split the problem into smaller problems as:
There are three ways to solve the problem.
Lets evaluate all three.
Procedural way of solving the problem,
Now suppose you need to add another handler, say ‘YAML’ file type. You will need to :
Advantages of the above approach:
What are the disadvantages of the above approach?
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:
public Map handle() {
extract();
replace();
}
Now the abstract method ‘extract’ needs to be overridden by its child classes:
Below is the main class.
Advantages of this approach:
Disadvantages:
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:
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.