Replacing If Else Statement with Pattern

Replacing if else statement with pattern

This is a classic Replace Condition dispatcher with Command in the Refactoring to Patterns book.

Sample Image

Basically you make a Command object for each of the blocks of code in your old if/else group and then make a Map of those commands where the keys are your condition Strings

interface Handler{
void handle( myObject o);
}

Map<String, Handler> commandMap = new HashMap<>();
//feel free to factor these out to their own class or
//if using Java 8 use the new Lambda syntax
commandMap.put("conditionOne", new Handler(){
void handle(MyObject o){
//get desired parameters from MyObject and do stuff
}
});
...

Then instead of your if/else code it is instead:

 commandMap.get(someCondition).handle(this);

Now if you need to later add new commands, you just add to the hash.

If you want to handle a default case, you can use the Null Object pattern to handle the case where a condition isn't in the Map.

 Handler defaultHandler = ...

if(commandMap.containsKey(someCondition)){
commandMap.get(someCondition).handle(this);
}else{
defaultHandler.handle(this);
}

Replacing if else statement with any design pattern or better approach

Should be more suitable to be reused :You can write a "Interval" Class with "inRange" methode like this :

 public struct Interval<T>
where T : IComparable
{
public T Start { get; set; }
public T End { get; set; }
public T Visit { get; set; }

public Interval(T visit, T start, T end)
{
Visit = visit;
Start = start;
End = end;
}

public bool InRange(T value)
{
return ((!Start.HasValue || value.CompareTo(Start.Value) > 0) &&
(!End.HasValue || End.Value.CompareTo(value) >= 0));
}
}

And then use like this :

public static readonly List<Interval<int>> range = new List<Interval<int>>
{
new Interval<int>(1, 0, 1),
new Interval<int>(2, 1, 2),
new Interval<int>(3, 2, 4),
new Interval<int>(4, 4, 6),
new Interval<int>(5, 6, 9),
new Interval<int>(6, 9, 12),
new Interval<int>(7, 12, 15),
new Interval<int>(8, 15, 18),
new Interval<int>(9, 18, 24),
new Interval<int>(10, 24, 30),
new Interval<int>(11, 30, 36),
new Interval<int>(12, 36, 48),
new Interval<int>(13, 48, 60),
new Interval<int>(14, 60, int.MaxValue)
};

var months = 5;
var visit = range.Where(x => x.InRange(months)).Select(x => x.Visit).FirstOrDefault();

Replace if/else statement with pattern

The Factory Pattern (Factory Method && Abstract Factory) is not going to work well for your use case, because this pattern will only force you to create a couple of classes for each if statement and the pattern usually returns an abstract object to your client code to consume (not your case because you're returning a simple string).

You also can't use a Simple Factory (which is not a design pattern) because will only move the conditional logic into a single class, so your code design will not improve.

You should not use a design pattern just increase the complexity of your code without the benefits of flexibility.

For your use case I would simply leave it as an if/else statement using a single line of code.

return mtdIndicator ? Helper.FOMBusDt :
prev2BusDtInd ? Helper.Previous2BusinessDate :
Helper.PreviousBusinessDate;

If you want to replace conditionals with polymorphism you should try:

  • Chain-of-Responsibility Pattern
  • Strategy Pattern
  • Decorator Pattern

... or other patterns (depends on the overall architecture)

Also you can avoid ifs by following 'Tell DON'T Ask'.

  • You should send a request to your low level component to do task X
  • You should NOT ask a low level component about it's internal
    context / state (you do that by avoiding functions like isStateXOk():Boolean)

TIP: Don't avoid a short if/else statement by creating from 3 to 10+ classes, avoid a switch by making classes for each case branch.

Design patterns that can replace if statements

After loong time I find opensource rule engine frameworks like "drools" is a great alternative to fit my requirement.

Replacing if/else logic with state/strategy pattern

I think you should use GoF pattern Chain of responsibility. You should introduce two interfaces: 1) Condition where you will check proper condition, e.g. "If the zip file does not exist" and return boolean result - "true" if condition is satisfied, otherwise "else", 2) Execution strategy, that will run action assigned with condition, e.g. "download it from specified URL and then unzip it and read in file and move zip file to specified directory." So, 1st interface will be answer to the question "when", and 2nd - "then". "Condition" implementation and "execution strategy" implementation should be combined into "tuple" (or pair, entry, etc). This "tuple" should be moved to collection in order, that you've described. Then, when you need to handle zip file, you'll iterate over collection, invoking conditions and checking results, if result is "true" then invoking appropriate "execution strategy". Also, condition can be combined with execution strategy and moved into single interface/implementation with two methods. Context, that will describe current state of zip file can be passed between conditions/execution strategies.
Hope this helps.

Update.
Code example (in Java).

/**
* All implementations should check proper condition
*/
interface Condition {

/**
* Check if condition is satisfied
*
* @param pathToFile path to target file
* @return 'true' if condition is satisfied, otherwise 'false'
*/
boolean isSatisfied(String pathToFile); //i've made an assumption that you'll manipulate file path for checking file
}
...
/**
* Childs will wrap some portion of code (if you'll use language, that supports lambdas/functors, this interface/implementation can be replaced with lambda/functor)
*/
interface Action {

/**
* Execute some portion of code
*
* @param pathToFile path to target file
*/
void execute(String pathToFile);
}
...
class ZipFileExistsCondition implements Condition {

@Override
public boolean isSatisfied(String pathToFile) {
... //check if zip file exists
}
}
...
class ZipFileDoesNotExists implements Condition {
@Override
public boolean isSatisfied(String pathToFile) {
... //download zip file and move it to some temp directory
//if file downloaded ok, than return 'true' otherwise 'false'
}
}
...
class AlwaysSatisfiedCondition implements Condition {
@Override
public boolean isSatisfied(String pathToFile) {
... //always returns 'true', to run action assigned with this condition
}
}
...
Collection<Map.Entry<Condition, Action>> steps = Arrays.asList(
new AbstractMap.ImmutableEntry<Condition, Action>(new ZipFileExistsCondition(),
new Action() { /*move zip file to zip file directory and read in file*/ }),
new ZipFileDoesNotExists(), new Action() { /*download it from specified URL and then unzip it and read in file and move zip file to specified directory*/ },
new AlwaysSatisfiedCondition(), new Action() { /*create blank file and write it out to disk*/ }
);
...
String pathToFile = ...
...
for(Map.Entry<Condition, Action> step: steps) {
if(!step.getKey().isSatisfied(pathToFile))
continue;

step.getValue().execute(pathToFile);
}

Remarks:
1) you can implement 'Condition' as anonymous classes,
2) 'AlwaysSatisfiedCondition' can be singleton,
3) if you're using Java/Groovy/Scala, you can use Guava/Apache Commons 'Predicate' instead of 'Condition', 'Function' or 'Closure' instead of 'Action'.

If you need to exit after first 'satisfied' condition and appropriate action execution then just put 'break'/'return' after action execution.



Related Topics



Leave a reply



Submit