In my java app I got a method that runs a long sequence of steps (synchronously) where one step result is the input for the next.
for example:
// Step 1
Map<String, SomeObject> objectsMap = someService.createObjectsMap();
if (!objectsMap.isEmpty()) {
// Step 2
AnotherObject anotherObject = anotherService.createAnotherObject(objectsMap);
if (null != anotherObject) {
// Step 3 that gets anotherObject as input and returns something else
} else { // Step 2 failed
// log and handle
}
} else { // Step 1 failed
// log and handle
}
And so I got this series of steps written in a series of if-else blocks. There is no common interface for the steps since each one has different signature. I've been loking on some different and tried to customize patterns such as chain-of-responsibility and command but could not get to a satisfied result.
I wonder if this ugly long if-else section is the way to go or is there a pattern out there that can help make this series of steps more clean and scalable.
One question you must answer yourself is Why do I want to refactor my code?
Do you want it to be
Refactor in order to make the code clean
If the steps do not need to be configured at runtime and you want to make your code more clean than you should take a look at the comments you made. Each comment is a hint.
Break the code block into methods and name them after the steps
/**
* Explain what step1 does.
*/
private void step1() {
// Step 1
Map<String, SomeObject> objectsMap = someService.createObjectsMap();
if (!objectsMap.isEmpty()) {
step2(objectsMap);
} else { // Step 1 failed
// log and handle
}
}
/**
* Explain what step2 does.
*/
private void step2(Map<String, SomeObject> objectsMap) {
// Step 2
final AnotherObject anotherObject = anotherService
.createAnotherObject(objectsMap);
if (null != anotherObject) {
step3(anotherObject);
} else { // Step 2 failed
// log and handle
}
}
/**
* Explain what step3 does.
*/
private void step3(AnotherObject anotherObject) {
// Step 3 that gets anotherObject as input and returns something
// else
}
This approach just breaks the method down to smaller methods. The advantage is that each smaller method is only responsible for one thing. And because it is a method you can add javadoc to it. So there is no need for inline comments anymore. Eventually you can give the method better names and omit the javadoc at all.
Refactor in order to make the steps replaceable at runtime
If you want to configure the steps that are executed at runtime (e.g. because of some user input) than you must encapsulate them in objects, because your application has references to objects that can be replaced.
Since you want all steps to have a common api you must make it more general.
Start thinking from the clients perspective. How should the steps be executed. E.g.
for (Step step : steps) {
boolean executeNext = step.execute();
if (!executeNext) {
break;
}
}
Design a Step interface
public interface Step {
boolean execute();
}
How to pass the output of one step as the input to another?
Make an interface
public static interface StepInput<T> {
public T getInput();
}
Implement your Steps. An abstract class will help you.
public abstract class InputOutputStep<T> implements Step,
StepInput<T> {
private T returnValue;
protected void setReturnValue(T returnValue) {
this.returnValue = returnValue;
}
public T getInput() {
return returnValue;
}
}
public class Step1 extends InputOutputStep<Map<String, SomeObject>> {
private StepInput<Map<String, SomeObject>> stepInput;
public Step1(StepInput<Map<String, SomeObject>> stepInput) {
this.stepInput = stepInput;
}
public boolean execute() {
boolean executeNext = false;
Map<String, SomeObject> objectsMap = stepInput.getInput();
if (!objectsMap.isEmpty()) {
// Step 2
setReturnValue(objectsMap);
executeNext = true;
} else { // Step 1 failed
// log and handle
}
return executeNext;
}
}
public class Step2 extends InputOutputStep<AnotherObject> {
private StepInput<Map<String, SomeObject>> stepInput;
private AnotherService anotherService;
public Step2(AnotherService anotherService,
StepInput<Map<String, SomeObject>> stepInput) {
this.anotherService = anotherService;
this.stepInput = stepInput;
}
public boolean execute() {
boolean executeNext = false;
Map<String, SomeObject> objectsMap = stepInput.getInput();
AnotherObject anotherObject = anotherService
.createAnotherObject(objectsMap);
if (null != anotherObject) {
setReturnValue(anotherObject);
executeNext = true;
} else { // Step 2 failed
// log and handle
}
return executeNext;
}
}
public class Step3 extends InputOutputStep<Void> {
private StepInput<AnotherObject> stepInput;
public Step3(StepInput<AnotherObject> stepInput) {
this.stepInput = stepInput;
}
public boolean execute() {
AnotherObject anotherObject = stepInput.getInput();
setReturnValue(null);
return false;
}
}
Configure the steps at runtime and execute
Step1 step1 = new Step1(stepInput);
Step2 step2 = new Step2(anotherService, step1);
Step step3 = new Step3(step2);
Step[] steps = new Step[]{step1, step2, step3};
for (Step step : steps) {
boolean executeNext = step.execute();
if (!executeNext) {
break;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With