Design pattern for incremental code

According to business logic, the output of one of the methods is used as input to the other. The logic has a linear flow. To emulate the behavior, there is now a controller class that has everything.

It's very messy, too many loc and difficult to change. Also, exception handling is very complicated. The individual method does some processing, but global exceptions are bubbling and involve many statements try

catch

.

Is there a design pattern to solve 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
}

      

+3


source to share


2 answers


I think it's called a pipeline: http://en.wikipedia.org/wiki/Pipeline_%28software%29 This pattern is used for algorithms where data flows through a sequence of tasks or steps.

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 the objects in the list and the results from one si are passed on to the next. This is a naive implementation, but you can add generics and whatever 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);
}

      

+2


source


When you run into problems like this, I love how other programming languages ​​can solve it. Then I could take this concept and apply it to the language I am using.

There has been a lot of talk in javascript about promises and how they can simplify not only asynchronous processing, but also error handling. This page is a great introduction to the problem.

Then the approach was 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 processing and handle errors efficiently in one place, without worrying about checking success at each step.

So how will this work in java? I would look at one of the promises / futures libraries, maybe jdeferred . I would expect that you can add 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 will of course mask a hidden requirement in your code. You note that in step 3, you need the output for both steps 1 and 2. If you are writing scala, there is syntactic sugar that will handle this for you (no error handling is done at 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 option here, you stay on the path of refactoring each step to only execute the output of the previous step, or nest processing so that result1 is still in scope when you need to set step 3.

The classic alternative to this, as @ user1121883 mentioned, is to use the Pipeline processor. The downside to this approach is that it works best if your input and output are the same type. Otherwise, you have to push the object all over the place and do a lot of type checking.

Another alternative would be to expose a free interface for the pipeline. Again, you'll want to do some refactoring, perhaps having a parameterless 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 latter option is more ideomatic java, but retains many of the advantages of thenables handling, so it will probably be the way I could go.

+1


source







All Articles