在Java中替换掉繁杂的if语句

时间:2021-6-2 作者:qvyue

if else每种编程语言都不可或缺的条件语句,在编程时会大量的用到。一般建议嵌套不要超过三层,如果一段代码存在过多的嵌套,代码的可读性就会急速下降,后期维护难度也大大提高。

一、简述

通常会遇到一些需要做一系列条件处理的业务逻辑,并且它们每一个都需要不同的处理。来看一下 Calulator(计算器)类的一个例子。下面是带有两个数字类型参数,一个操作符参数以及基于操作的数值返回值的一个方法:

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

通常也能使用 switch 语句来操作:

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

switch 语句其实并没有任何实质性的优化。嵌套条件声明编程使得程序变得难以管理。如果需要新添加一个操作,相应需要添加一个新的 if 条件以及条件的实现,甚至需要回归整个业务代码。

二、工厂类

经常遇见许多条件声明,它们是用来处理每个分支中相似的操作。可以提取一个返回具体类型的对象并且根据具体对象行为执行操作的工厂类。例如,定义一个带有单独的 apply 方法的操作接口:

public interface CalOperation {
    int apply(int a, int b);
}

单独定义业务类:
加:

public class Addition implements CalOperation {
    @Override
    public int apply(int a, int b) {
        return a + b;
    }
}

减:

public class Subtraction implements CalOperation {
    @Override
    public int apply(int a, int b) {
        return a - b;
    }
}

乘:

public class Multiplication implements CalOperation {
    @Override
    public int apply(int a, int b) {
        return a * b;
    }
}

除:

public class Division implements CalOperation {
    @Override
    public int apply(int a, int b) {
        return a / b;
    }
}

现在实现一个基于给定操作符返回相应结果的工程类:

public class OperatorFactory {
    static Map operationMap = new HashMap();
    //在静态块中先把初始化工作全部做完  
    static {
        operationMap.put("add", new Addition());
        operationMap.put("subtract", new Subtraction());
        operationMap.put("multiply", new Multiplication());
        operationMap.put("divide", new Division());
    }
    public static CalOperation getOperation(String operator) {
        return operationMap.get(operator);
    }
}

在 Calculator 类中,能够通过查询工厂来获取相关的操作并且应用于其中:

public class Calculator {
   public String cal( String operator) {
   // 一行代码搞定!之前的 if/else也没了!
   return OperatorFactory.getOperation(operator).apply(); 
   }
}

该例通过工厂类来将逻辑业务责任分发委托给一系列轻耦合对象。但是如果只是简单地将嵌套 if 语句转移成工厂类,这明显不符合预期。作为另外的选择,通过维护能够被快速查询的对象仓库map(映射),来达成目的。也可以在运行时定义映射对象并且配置它们用于查找。这样的话以后想扩展条件也很容易,只需要增加新代码,而不需要动以前的业务代码,非常符合“开闭原则”。

三、命令模式(command pattern)

使用工厂类可以返回指定操作符的对应的业务对象实例,该实例用于 Claculator 类中执行计算操作。也可以设计一个 Calculator.calculate 方法来接收一个可以执行输入的指令。这是另外一种来代替嵌套 if 语句的方法。定义一个 Command 接口:

public interface Command {
    Integer execute();
}

其中一个实现类 AddCommand:

public class AddCommand implements Command {
    public AddCommand(int a, int b) {
        this.a = a;
        this.b = b;
    }
    @Override
    public Integer execute() {
        return a + b;
    }
}

最后,在 Calculator 类中定义一个用于接收操作和执行操作的新方法:

public class Calculator {
    public int calculate(Command command) {
        return command.execute();
    }
}

通过实例化 AddCommand 对象并且将它作为参数传递到 Calculator.calculate 方法当中用以调用计算方法:

@Test
public void whenCalculateUsingCommand_thenReturnCorrectResult() {
    Calculator calculator = new Calculator();
    int result = calculator.calculate(new AddCommand(3, 7));
    assertEquals(10, result);
}

三、策略模式

策略模式和工厂模式写起来其实区别也不大!

在上面工厂模式代码的基础上,按照策略模式的指导思想,来创建一个所谓的策略上下文类,这里命名为 CalContext:

public class CalContext {
    private CalOperation calOperation;
    public CalContext(CalOperation calOperation) {
        this.calOperation = calOperation;
    }
    public int execute(int a, int b) {
        return calOperation.apply(a, b);
    }
}

很明显上面传入的参数 calOperation 就是表示不同的“策略”。在业务代码里传入不同的角色,即可得到不同的操作结果:

public class Calculator {
    public int apply(int a, int b, CalOperation calOperation) {
        CalContext calContext = new CalContext(calOperation);
        return calContext.execute(a, b);
    }
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        int add = calculator.apply(6, 2, new Addition());
        System.out.println(add);
        int sub = calculator.apply(6, 2, new Subtraction());
        System.out.println(sub);
        int mul = calculator.apply(6, 2, new Multiplication());
        System.out.println(mul);
        int div = calculator.apply(6, 2, new Division());
        System.out.println(div);
    }
}

四、使用枚举

除了映射对象(map)的使用之外,也可以使用枚举来标记特定的逻辑业务。在这之后,能通过它来代替嵌套 if 语句或者 swtich 语句。作为其它处理,也可以使用它们作为对象工厂并且整理用于处理相关的业务逻辑操作。这会减少嵌套 if 语句的数量并且将业务责任委托给独立的枚举变量中。定义一个枚举类:

public enum CalEnum{
    ADD, SUBTRACT, MULTIPLY, DIVIDE
}

这些值是不同操作符的标签,并且会运用到之后的计算当中。就像嵌套 if 语句和 switch 语句那样,可以将这些值当作选项来使用。然而,可以设计一种能够将逻辑委托给枚举本身的替代方法,为每一个枚举量都定义了各自的方法并且进行了计算操作。例如:

CalOperation 类:

public interface CalOperation {
    int apply(int a, int b);
}

CalEnum 类:

public enum CalEnum implements CalOperation {
    //加
    ADD {
        @Override
        public int apply(int a, int b) {
            return a + b;
        }
    },
    //减
    MULTIPLY {
        @Override
        public int apply(int a, int b) {
            return a - b;
        }
    },
    //乘
    SUBTRACT {
        @Override
        public int apply(int a, int b) {
            return a * b;
        }
    },
    //除
    DIVIDE {
        @Override
        public int apply(int a, int b) {
            return a / b;
        }
    }
}

调用如下:

public class Calculator {
    public int apply(int a, int b, String calOperation) {
        // 一行代码搞定!之前的if/else没了!
        return CalEnum.valueOf(calOperation).apply(a, b);
    }
    public static void main(String[] args) {
        CalClass cc = new CalClass();
        int add = cc.apply(1, 2, "MULTIPLY");
        System.out.println(add);
    }
}

五、规则引擎(rule engine)

当编写了大量的嵌套 if 语句时,每一个条件都描述了特定的业务规则,用于评估正确逻辑操作的执行。规则引擎将这些复杂的判断从主代码中去掉。规则引擎是用于评估规则并且基于输入返回结果。这个规则引擎是通过一组规则来处理表达式,并从选中的规则返回结果。定义一个规则接口:

public interface Rule {
    boolean evaluate(Expression expression);
    Result getResult();
}

接着,来实现一个规则引擎:

public class RuleEngine {
    private static List 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();
    }
}

这个规则引擎接收一个表达式并且返回 Result。现在,设计一个带有两个数值变量以及一个用于操作的 Operator 对象的表达式类:

public class Expression {
  private Integer x;
  private Integer y;
  private Operator operator; 
}

最后,定义一个 AddRule 类,它只在加操作中使用到:

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 来调用 RuleEngine 了:

@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());
}

六、提前 return

这是判断条件取反的做法,代码在逻辑表达上会更清晰,看下面代码:

if(condition){
  //do something
}else{
  return xxx;
}

其实,完全可以先判断 !condition,干掉else。

if(!condition){
  return xxx;
}
  //do something

七、Optional主要用于非空判断,jdk8新特性

使用之前:

if(user == null){
  //do action1
}else{
  //do action2
}

如果登录用户为空,执行 action1,否则执行 action 2。使用 Optional 优化之后,让非空校验更加优雅,间接的减少 if 操作:

Optional userOptional = Optional.ofNullable(user);
userOptional.map(action1) .orElse(action2);

八、数组小技巧

来自 google 解释,这是一种编程模式,叫做表驱动法,本质是从表里查询信息来代替逻辑语句,比如有这么一个场景,通过月份来获取当月的天数,仅作为案例演示,数据并不严谨。一般的实现:

public int getDays(int month) {
       if (month == 1) {
           return 31;
       } else if (month == 2) {
           return 29;
       } else if (month == 3) {
           return 31;
       } else if (month == 4) {
           return 30;
       } else if (month == 5) {
           return 31;
       } else if (month == 6) {
           return 30;
       } else if (month == 7) {
           return 31;
       } else if (month == 8) {
           return 31;
       } else if (month == 9) {
           return 30;
       } else if (month == 10) {
           return 31;
       } else if (month == 11) {
           return 30;
       } else if (month == 12) {
           return 31;
       }
       return 0;
}

优化后的代码:

int[] monthDays = {
        31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
public int getDays(int month) {
    return monthDays[--month];
}
声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。