Common refactoring techniques - 5 ways to remove redundant if else

Common refactoring techniques - remove redundant if else

The most common is the use of a lot of if/else or switch/case in the code; How to refactor? There are many methods. This article takes you to learn the skills.

if/else and switch/case scenarios appear

Usually, the business code will contain such logic: there will be different processing logic under each condition. For example, two numbers a and b can be calculated by different operators (+,, *, /). Beginners usually write this:

public int calculate(int a, int b, String operator) {
    int result = Integer.MIN_VALUE;
 
    if ("add".equals(operator)) {
        result = a + b;
    } else if ("multiply".equals(operator)) {
        result = a * b;
    } else if ("divide".equals(operator)) {
        result = a / b;
    } else if ("subtract".equals(operator)) {
        result = a - b;
    }
    return result;
}

Or use switch/case:

public int calculateUsingSwitch(int a, int b, String operator) {
    switch (operator) {
    case "add":
        result = a + b;
        break;
    // other cases    
    }
    return result;
}

How to refactor the most basic code?

Reconstruction ideas

There are many refactoring methods to solve this problem. Many methods will be listed here. In practical application, some adjustments may be made according to the scene; In addition, don't dwell on the obvious defects in these examples (such as useless constants, no consideration of multithreading, etc.), but focus on learning the ideas@ pdai

Mode I - Factory

  • Define an operation interface
public interface Operation {
    int apply(int a, int b);
}
  • To implement the operation, here only take add as an example
public class Addition implements Operation {
    @Override
    public int apply(int a, int b) {
        return a + b;
    }
}
  • Realize operation factory
public class OperatorFactory {
    static Map<String, Operation> operationMap = new HashMap<>();
    static {
        operationMap.put("add", new Addition());
        operationMap.put("divide", new Division());
        // more operators
    }
 
    public static Optional<Operation> getOperation(String operator) {
        return Optional.ofNullable(operationMap.get(operator));
    }
}
  • Call in Calculator
public int calculateUsingFactory(int a, int b, String operator) {
    Operation targetOperation = OperatorFactory
      .getOperation(operator)
      .orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
    return targetOperation.apply(a, b);
}

Why is the method name above "apply" and how is optional used? Please refer to this article:

  • Java 8 - functional programming (lambda expressions)
    • What are the characteristics of Lambda expressions?
    • Lambda expressions use interfaces under and Stream?
    • Function interface definition and use, four built-in function interfaces: Consumer, function, supplier and predict
    • Comparator sorting as an example runs through all knowledge points.
  • Deep parsing of Java 8 - Optional Class
    • What is the meaning of the Optional class?
    • What are the common methods of the Optional class?
    • Optional examples run through all knowledge points
    • How to solve the judgment of multiple class nested Null value?

Mode 2 - Enumeration

Enumeration is suitable for situations where the type is fixed and enumerable, such as the operator of this; At the same time, methods can be provided in enumeration, which is why we can reconstruct through enumeration.

  • Define operand enumeration
public enum Operator {
    ADD {
        @Override
        public int apply(int a, int b) {
            return a + b;
        }
    },
    // other operators
    
    public abstract int apply(int a, int b);

}
  • Call in Calculator
public int calculate(int a, int b, Operator operator) {
    return operator.apply(a, b);
}
  • Write a test case to test:
@Test
public void whenCalculateUsingEnumOperator_thenReturnCorrectResult() {
    Calculator calculator = new Calculator();
    int result = calculator.calculate(3, 4, Operator.valueOf("ADD"));
    assertEquals(7, result);
}

See if it's easy?

Method 3 - command mode

Command mode is also a very common refactoring method, which treats each operator as a command.

  • First, let's review what command mode is

    • Look at this article: Behavioral command mode

      • Command pattern: encloses "requests" into objects so that different requests, queues, or logs can be used to parameterize other objects Command mode also supports undoable operations.
        • Command: Command
        • Receiver: the receiver of the command, that is, the real executor of the command
        • Invoker: it is used to invoke commands
        • Client: you can set the command and the receiver of the command

  • Command interface

public interface Command {
    Integer execute();
}
  • Command implementation
public class AddCommand implements Command {
    // Instance variables
 
    public AddCommand(int a, int b) {
        this.a = a;
        this.b = b;
    }
 
    @Override
    public Integer execute() {
        return a + b;
    }
}
  • Call in Calculator
public int calculate(Command command) {
    return command.execute();
}
  • test case
@Test
public void whenCalculateUsingCommand_thenReturnCorrectResult() {
    Calculator calculator = new Calculator();
    int result = calculator.calculate(new AddCommand(3, 7));
    assertEquals(10, result);
}

Note that the new AddCommand(3, 7) here still does not solve the problem of obtaining operators dynamically, so it can be called in combination with the simple factory mode:

  • Create - simple factory
    • Simple factory, which puts the instantiation operation into a class separately, becomes a simple factory class. Let the simple factory class decide which specific subclass should be instantiated. This can decouple the implementation of the customer class and the specific subclass. The customer class no longer needs to know which subclasses and which subclass should be instantiated

Method 4 - rule engine

The rule engine is suitable for the situation where there are many rules and may change dynamically. First, we should understand a little Java OOP, that is, the abstraction of classes:

  • What classes can be abstracted here// This automatic transformation is needed in the mind

    • Rule rule
      • Rule interface
      • Generalization implementation of specific rules
    • Expression
      • Operator
      • Operand
    • Rule engine
  • Define rules

public interface Rule {
    boolean evaluate(Expression expression);
    Result getResult();
}
  • Add rule
public class AddRule implements Rule {
    @Override
    public boolean evaluate(Expression expression) {
        boolean evalResult = false;
        if (expression.getOperator() == Operator.ADD) {
            this.result = expression.getX() + expression.getY();
            evalResult = true;
        }
        return evalResult;
    }    
}
  • expression
public class Expression {
    private Integer x;
    private Integer y;
    private Operator operator;        
}
  • Rule engine
public class RuleEngine {
    private static List<Rule> rules = new ArrayList<>();
 
    static {
        rules.add(new AddRule());
    }
 
    public Result process(Expression expression) {
        Rule rule = rules
          .stream()
          .filter(r -> r.evaluate(expression))
          .findFirst()
          .orElseThrow(() -> new IllegalArgumentException("Expression does not matches any Rule"));
        return rule.getResult();
    }
}
  • test case
@Test
public void whenNumbersGivenToRuleEngine_thenReturnCorrectResult() {
    Expression expression = new Expression(5, 5, Operator.ADD);
    RuleEngine engine = new RuleEngine();
    Result result = engine.process(expression);
 
    assertNotNull(result);
    assertEquals(10, result.getValue());
}

Method 5 - Strategic Model

The policy pattern is more commonly used than the command pattern, and it needs to be injected into the actual business logic development (for example, inject bean s through Spring's @ Autowired). At this time, the policy pattern can be cleverly reconstructed

  • What is the strategy model?

    • Let's review: Behavioral strategy
    • Strategy pattern: defines the algorithm family, which is closed separately so that they can be replaced with each other. This pattern makes the change of the algorithm independent of the customers using the algorithm
      • The Strategy interface defines a family of algorithms, all of which have behavior() methods.
      • Context is a class that uses the algorithm family. The doSomething() method will call behavior(), and the setStrategy(in Strategy) method can dynamically change the strategy object, that is, the algorithm used by context.

  • Does Spring need to inject resource refactoring?

What if resource injection is needed in the business framework? For example, inject bean s through Spring's @ Autowired. This can be achieved:

  • Operation / / very clever
public interface Opt {
    int apply(int a, int b);
}

@Component(value = "addOpt")
public class AddOpt implements Opt {
    @Autowired
    xxxAddResource resource; // Here, resources are injected through the Spring framework

    @Override
    public int apply(int a, int b) {
       return resource.process(a, b);
    }
}

@Component(value = "devideOpt")
public class devideOpt implements Opt {
    @Autowired
    xxxDivResource resource; // Here, resources are injected through the Spring framework

    @Override
    public int apply(int a, int b) {
       return resource.process(a, b);
    }
}
  • strategy
@Component
public class OptStrategyContext{
 

    private Map<String, Opt> strategyMap = new ConcurrentHashMap<>();
 
    @Autowired
    public OptStrategyContext(Map<String, TalkService> strategyMap) {
        this.strategyMap.clear();
        this.strategyMap.putAll(strategyMap);
    }
 
    public int apply(Sting opt, int a, int b) {
        return strategyMap.get(opt).apply(a, b);
    }
}

The above code is very common in implementation.

Some reflection

What I fear most is that I want to use idioms everywhere as soon as I learn idioms.

  • Do you really want to refactor like this?

    • In the actual development, remember that what you fear most is that you want to use idioms everywhere when you just learn idioms; Most of the time, instead of considering whether it is the best implementation, it is a compromise (usually a compromise between business and cost, a compromise between development and maintenance...), Make appropriate refactoring at the right time.
    • In many cases, it is best to maintain the code for the sustainability of the team;
    • After refactoring, many classes will be generated. Is a simple business so complex? So you need to weigh

Reference articles

More content

The most complete Java back-end knowledge system https://www.pdai.tech , updating every day.

Posted by varecha on Mon, 16 May 2022 18:57:48 +0300