Design pattern for incremental code

1.4k Views Asked by At

According to the business logic, the output of one of the method is used as an input to another. The logic has linear flow. To emulate the behaviour, now there is a controller class which has everything.

It is very messy, too much loc and hard to modify. Also the exception handling is very complex. The individual method does some handling but the global exceptions bubble up and which involves a lot of try catch statements.

Does there exists a design pattern to address this problem?

Example Controller Class Code

try{
   Logic1Inputs logic1_inputs = new Logic1Inputs( ...<some other params>... );
   Logic1 l = new Logic1(logic1_inputs);
   try{ 
     Logic1Output l1Output = l.execute();
   } catch( Logic1Exception l1Exception) {
     // exception handling
   }

   Logic2Inputs logic2_inputs = new Logic2Inputs(l1Output);
   Logic2 l2 = new Logic2(logic2_inputs);
   try{ 
     Logic2Output l2Output = l2.execute();
   } catch( Logic2Exception l2Exception) {
     // exception handling
   }

   Logic3Inputs logic3_inputs = new Logic3Inputs(l1Output, l2Output);
   Logic3 l3 = new Logic3(logic2_inputs);
   try{ 
     Logic3Output l3Output = l3.execute();
   } catch( Logic3Exception l3Exception) {
     // exception handling
   }
} catch(GlobalException globalEx){
  // exception handling
}
2

There are 2 best solutions below

3
On BEST ANSWER

I think this is called pipeline: http://en.wikipedia.org/wiki/Pipeline_%28software%29 This pattern is used for algorithms in which data flows through a sequence of tasks or stages.

You can search for a library that does this( http://code.google.com/p/pipelinepattern ) or try your own java implementation

Basically you have all you objects in a list and the output from one si passed to the next. This is a naive implementation but you can add generics and all you need

public class BasicPipelinePattern {
    List<Filter> filters;

    public Object process(Object input) {
        for (Filter c : filters) {
            try {
                input = c.apply(input);
            } catch (Exception e) {
                // exception handling
            }
        }
        return input;
    }

}

public interface Filter {
    public Object apply(Object o);
}
0
On

When faced with problems like this, I like to see how other programming languages might solve it. Then I might borrow that concept and apply it to the language that I'm using.

In javascript, there has been much talk of promises and how they can simplify not only asynchronous processing, but error handling. This page is a great introduction to the problem.

Then approach has been called using "thenables". Here's the pseudocode:

initialStep.execute().then(function(result1){
    return step2(result1);
}).then(function(result2){
    return step3(result3);
}).error(function(error){
    handle(error);
}).done(function(result3){
    handleResult(result3)
});

The advantage of this pattern is that you can focus on the processing and effectively handle errors in one place without needing to worry about checking for success at each step.

So how would this work in java? I would take a look at one of the promises/futures libraries, perhaps jdeferred. I would expect that you could put something like this together (assuming java 8 for brevity):

initialPromise.then( result1 -> {
    Logic2 logic2 = new Logic2(new Logic2Inputs(result1));
    return logic2.execute();
}).then(result2 -> {
    Logic3 logic3 = new Logic3(new Logic3Inputs(result2));
    return logic2.execute();
}).catch(exception -> {
    handleException(exception)
}).finally( result -> {
    handleResult(result);
});

This does, of course gloss over a hidden requirement in your code. You mention that in step 3 you need the output for both step 1 and step 2. If you were writing scala, there is syntactic sugar that would handle this for you (leaving out error handling for the moment):

for(result1 <- initialStep.execute(); 
    Logic2 logic2 = new Logic2(Logic2Input(result1));
    result2 <- logic2.execute();
    Logic3 logic3 = new Logic3(Logic3Input(result1, result2));
    result3 <- logic3.execute()) yield result3;

But since you don't have the ability here, then you are left to the choices of being refactoring each step to take only the output of the previous step, or nesting the processing so that result1 is still in scope when you need to set up step 3.

The classic alternative to this, as @user1121883 mentioned would be to use a Pipeline processor. The downside to this approach is that it works best if your input and output are the same type. Otherwise you are going to have to push Object around everywhere and do a lot of type checking.

Another alternative would be to expose a fluent interface for the pipeline. Again, you'd want to do some refactoring, perhaps to have a parameter-less constructor and a consistent interface for inputs and outputs:

Pipeline p = new Pipeline();
p.then(new Logic1())
 .then(new Logic2())
 .then(new Logic3())
 .addErrorHandlder(e->handleError(e))
 .complete();

This last option is more ideomatic java, but retains many of the advantages of the thenables processing, so it's probably the way that I would go.