Saturday, October 20, 2012

The OperationHandler Design Pattern


According to the principle of reuse, the code must not be copy-pasted from one location to another but it should be used by calling methods, composition, inheritance and refactoring existing code so that it may be reused. For maximum reuse of code, we use design patterns so that the code written by us is more reusable. There are lots of design patterns discussed already in various books and at various places like blogs and forums etc. I want to discuss a new design pattern that I have never encountered before in any book or any site. I have named this pattern as The OperationHandler Design Pattern. Before introducing the design pattern, I show you a problem, its solution and then the solution reused by copying and pasting code from existing solution and then I’ll show you both the solutions with maximum reuse using the design pattern being introduced.
Problem: We have to zip all files inside a directory recursively. We have to create a function which accepts a File object to zip and a File object to create a zip file to. If the input File object is a file, we have to directly zip it and if the input File object is a directory, then we have to zip the contents of the directory recursively.

Solution: We create a function which accepts the two File objects as in the question and call a recursive function. The recursive function accepts a File object to zip and a ZipOutputStream to write the contents of the input file.

Code:
public static void createZipFile(File source, File dest, int compressionLevel) throws IOException {ZipOutputStream out = new ZipOutputStream(new FileOutputStream(dest));
out.setLevel(compressionLevel);
createZip("", source, out);
out.close();
}
private static void createZip(String prefix, File source, ZipOutputStream out) throws IOException {
String fileName = source.getName();
if(source.isDirectory()) {
fileName = fileName+"/";
}
if(source.isDirectory()) {
File[] children = source.listFiles();
int len = children == null ? 0 : children.length;for (int i = 0; i < len; i++) {
createZip(prefix+fileName, children[i], out);
}
}
else {
ZipEntry entry = 
new ZipEntry(prefix+fileName);
entry.setTime(source.lastModified());
out.putNextEntry(entry);
InputStream in = 
new FileInputStream(source);byte[] buffer = new byte[1024];while(in.available() > 1024) {
in.read(buffer);
out.write(buffer);
}
buffer = 
new byte[in.available()];
in.read(buffer);
out.write(buffer);
in.close();
out.closeEntry();
}
Now we got another problem in which we have to set the last modified date of all files inside a directory recursively. It is human nature that tends to find a solution of a problem first around ourselves before inventing it. We also look around and copy the code in the previous example and do as in the following.
Problem: We have to set last modified date of all files inside a directory recursively. We have to create a function which accepts a File object to set the last modified date and a date.
Solution: We create a function which accepts the two arguments and calls itself recursively. To create this function we copy the code from previous example and if the input file object represents a directory we call the function recursively for the files inside the directory and if the input file represents a file then we set its last modified date.

Code:
public static void setLastModifiedDate(File source, java.util.Date lastModifiedDate) {
if
(source.isDirectory()) {
File[] children = source.listFiles();
int len = children == null ? 0 : children.length;
for
 (int i = 0; i < len; i++) {
setLastModifiedDate(children[i], lastModifiedDate);
}
}
else {
source.setLastModified(lastModifiedDate.getTime());
}
}
However the code does not seem to be copy-pasted code in the example given, we accept this example to get understanding of the pattern I am going to present.
Let us compare the code in both the examples:

// private static void createZip(String prefix, File source, ZipOutputStream out) throws IOException {
public static void setLastModifiedDate(File source, java.util.Date lastModifiedDate) {/*String fileName = source.getName();
if(source.isDirectory()) {
fileName = fileName+"/";
}*/
if(source.isDirectory()) {
File[] children = source.listFiles();
int len = children == null ? 0 : children.length;for (int i = 0; i < len; i++) {/*createZip(prefix+fileName, children[i], out);*/
setLastModifiedDate(children[i], lastModifiedDate);
}
else {
/*ZipEntry entry = new ZipEntry(prefix+fileName);
entry.setTime(source.lastModified());
out.putNextEntry(entry);
InputStream in = new FileInputStream(source);
byte[] buffer = new byte[1024];
while(in.available() > 1024) {
in.read(buffer);
out.write(buffer);
}
buffer = new byte[in.available()];
in.read(buffer);
out.write(buffer);
in.close();
out.closeEntry();*/
source.setLastModified(lastModifiedDate.getTime());
}
}
Now let me align both the problems as:
we have to perform an operation on all files inside a directory recursively. (i) The operation is to add the file contents inside a ZipOutputStream (ii) The operation is to set the last modified date of the files.
In the operation handler pattern, we create an operation handler interface for that type of operations and a recursive method that accepts the input to carry on the operation and the operation handler object. We create implementations of Operation Handler interface to perform each operation and pass necessary information to execute the operation in the constructor of the operation handler. The object on which the operation is to be performed is passed in the method of the operation handler created to perform the operation.
The previous examples can be rewritten using this pattern as:
static interface FileOperationHandler {
void performOperation(String filePrefix, File inputFile) throws IOException ;
}
static void performFileOperation(String filePrefix, File inputFile, FileOperationHandler opHandler) throwsIOException {
String fileName = inputFile.getName();
if(inputFile.isDirectory()) {
fileName = fileName+"/";
}
if(inputFile.isDirectory()) {
File[] children = inputFile.listFiles();
int len = children == null ? 0 : children.length;for (int i = 0; i < len; i++) {performFileOperation(filePrefix+fileName, children[i], opHandler);
}
}
else {
opHandler.performOperation(filePrefix, inputFile);
}
} 
static class LastModifiedOperationHandler implements FileOperationHandler {private final java.util.Date lastModifiedDate;
LastModifiedOperationHandler(java.util.Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public void performOperation(String filePrefix, File inputFile) throws IOException {
inputFile.setLastModified(lastModifiedDate.getTime());
}
}
static class ZipOperationHandler implements FileOperationHandler {private final ZipOutputStream out;
ZipOperationHandler(ZipOutputStream out) {
this.out = out;
}
public void performOperation(String filePrefix, File inputFile) throws IOException {
ZipEntry entry = 
new ZipEntry(filePrefix + inputFile.getName());
entry.setTime(inputFile.lastModified());
out.putNextEntry(entry);
InputStream in = 
new FileInputStream(inputFile);byte[] buffer = new byte[1024];while(in.available() > 1024) {
in.read(buffer);
out.write(buffer);
}
buffer = 
new byte[in.available()];
in.read(buffer);
out.write(buffer);
in.close();
out.closeEntry();
}
}
public static void createZipFile(File source, File dest, int compressionLevel) throws IOException {
ZipOutputStream out = 
new ZipOutputStream(new FileOutputStream(dest));
out.setLevel(compressionLevel);
performFileOperation("", source, 
new ZipOperationHandler(out));
out.close();
}
public static void setLastModifiedDate(File source, java.util.Date lastModifiedDate) {try {
performFileOperation("", source, new LastModifiedOperationHandler(lastModifiedDate));
}
catch (IOException ex) {throw new RuntimeException(ex);
}
}
The operation handler pattern is usable when ever we need to perform multiple operations recursively on a hierarchy of the same kind of objects. We may need to change the way we traverse the hierarchy for the sake of optimization or some change in implementation logic. If we copy-paste the traversing code every where and then we have to change the traversing code then it has to be done everywhere. Copy-pasting causes all the problems which occur due to redundancy. Whenever we copy paste the code, we also copy paste the bugs in it. It is difficult to track and change all the places where the copied-pasted code is used and so on.
This pattern is usable at a variety of places like: to perform operations on or collect data from a tree like structure, to perform attach/detach operations on persistent objects in an ORM implementation, to set properties of a tree recursively (for example to enable double buffering on a component hierarchy).
The Operation Handler Design pattern can be further extended for various call backs in Operation Handler interface like operationStarted(…), operationCompleted(…) etc. and a progress monitor can be passed inside the recursively called function which may be informed from inside the function and so on.
The Operation Handler Pattern is very useful in cases where there are many functions for similar kind of functionality with little differences sharing a lot of code. This pattern may be used in such places to avoid copy-paste and to enhance the quality of code and to encourage reuse.
The cons of The Operation Handler Pattern:
1. Not all the functionalities share the same set of exceptions being thrown. As in the given example, the function to zip the files throws IOException but the function to set last modified date need not throw it. Therefore handling of the IOException inside the LastModifiedOperationHandler is unnecessary overhead of this pattern.
2. There may be necessity to keep extra variables in the operation handler interface’s methods which may not be used in all operations. For example, the file prefix passed in method performFileOperation(…) of FileOperationHandler is not used in LastModifiedOperationHandler.
Considering the above overheads of this pattern, we should use this pattern only where large amount of code is being copied and pasted and it is more prone towards changes and therefore must be shared instead of copying and pasting. Or if the set of exceptions and required objects in perform operation method of operation handler interface is almost identical for most of the operations performed using the pattern.
Let us explore another example where The Operation Handler pattern is used to generalize a number of tasks which would share a lot of copy-pasted code otherwise.
Problem:
1. An Order object is reviewed by an official and it is then approved or cancelled. We have to mark the Order as approved or cancelled on click of a button etc.
2. When we mark an order as approved or cancelled, we have to mark the same for all the orderLineItems as well as for all related order objects (found according to some relation). 
Similarly when the Order is audited, we have to mark the order and its orderLineLitems as audited.
3. Whenever we mark an order as approved, cancelled or audited, we have to provide information like remarks, date, userName etc. to set fields like approvedBy, approvedOn, cancelledOn, cancelledBy etc.
Solution:
Instead of copy-pasting the code to iterate Order and OrderLineItems we create an OperationHandler and a recursive function (or a set of functions) to perform the operation. Now we can create multiple implementations of the OperationHandler for marking the Order/OrderLineItem as approved / cancelled / audited to reuse the iterating code.
Now consider the case where we have to mark not only the order objects but we have to also code for marking Tender and TenderLineItems, Contract and ContractLineItems, Invoice and InvoiceLineItems for the same (approved/cancelled/audited etc.) Now we can reuse the same code written for Order by creating Document and DocumentLineItem interfaces and modifying the Order problem for Document and DocumentLineItem. Now Order, Invoice, Tender and Contract all can implement the Document interface and OrderLineItem, InvoiceLineItem, TenderLineItem and ContractLineItem all can implement the DocumentLineItem interface.
Thus we see that we can reuse a large amount of code by using The Operation Handler Design Pattern in our daily routine problems. Our objective must be to completely prohibit the copy-pasting of the code in our organization and to encourage as much code reuse as possible.

No comments: