How to Replace Many if Statements in Java – 如何在Java中替换许多if语句

最后修改: 2018年 11月 8日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Decision constructs are a vital part of any programming language. But we land up in coding a huge number of nested if statements which make our code more complex and difficult to maintain.

决策结构是任何编程语言的一个重要组成部分。但我们在编码中会出现大量的嵌套if语句,这使得我们的代码更加复杂,难以维护。

In this tutorial, we’ll walk through the various ways of replacing nested if statements.

在本教程中,我们将了解替换嵌套if语句的各种方法。

Let’s explore different options how we can simplify the code.

让我们探讨一下如何简化代码的不同方案。

2. Case Study

2.案例研究

Often we encounter a business logic which involves a lot of conditions, and each of them needs different processing. For the sake of a demo, let’s take the example of a Calculator class. We will have a method which takes two numbers and an operator as input and returns the result based on the operation:

我们经常会遇到一个涉及很多条件的业务逻辑,而每个条件都需要不同的处理。为了演示,让我们以一个Calculator类为例。我们将有一个方法,它接受两个数字和一个操作符作为输入,并根据操作返回结果。

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;
}

We can also implement this using switch statements:

我们也可以使用switch语句来实现这一点

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

In typical development, the if statements may grow much bigger and more complex in nature. Also, the switch statements do not fit well when there are complex conditions.

在典型的开发中,if语句可能会越来越大,性质越来越复杂。另外,当有复杂的条件时,switch语句并不适合

Another side effect of having nested decision constructs is they become unmanageable. For example, if we need to add a new operator, we have to add a new if statement and implement the operation.

拥有嵌套决策结构的另一个副作用是它们变得难以管理。例如,如果我们需要添加一个新的操作符,我们必须添加一个新的if语句并实现该操作。

3. Refactoring

3.重构

Let’s explore the alternate options to replace the complex if statements above into much simpler and manageable code.

让我们探讨一下替代方案,将上面复杂的if语句替换成更简单、更容易管理的代码。

3.1. Factory Class

3.1.工厂类

Many times we encounter decision constructs which end up doing the similar operation in each branch. This provides an opportunity to extract a factory method which returns an object of a given type and performs the operation based on the concrete object behavior.

很多时候,我们遇到的决策结构最终会在每个分支中进行类似的操作。这就提供了一个机会,提取一个工厂方法,返回一个给定类型的对象,并根据具体对象的行为执行操作

For our example, let’s define an Operation interface which has a single apply method:

对于我们的例子,让我们定义一个Operation接口,它有一个apply方法。

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

The method takes two number as input and returns the result. Let’s define a class for performing additions:

该方法接受两个数字作为输入并返回结果。让我们定义一个用于执行加法的类。

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

We’ll now implement a factory class which returns instances of Operation based on the given operator:

我们现在要实现一个工厂类,它可以根据给定的操作符返回Operation的实例。

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

Now, in the Calculator class, we can query the factory to get the relevant operation and apply on the source numbers:

现在,在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);
}

In this example, we have seen how the responsibility is delegated to loosely coupled objects served by a factory class. But there could be chances where the nested if statements are simply shifted to the factory class which defeats our purpose.

在这个例子中,我们已经看到了如何将责任委托给由工厂类提供服务的松散耦合对象。但也有可能出现嵌套的if语句被简单地转移到工厂类中的情况,这就违背了我们的目的。

Alternatively, we can maintain a repository of objects in a Map which could be queried for a quick lookup. As we have seen OperatorFactory#operationMap serves our purpose. We can also initialize Map at runtime and configure them for lookup.

另外,我们可以在Map中维护一个对象库,可以通过查询来快速查找。正如我们所看到的,OperatorFactory#operationMap可以满足我们的目的。我们也可以在运行时初始化Map,并配置它们进行查询。

3.2. Use of Enums

3.2.枚举的使用

In addition to the use of Map, we can also use Enum to label particular business logic. After that, we can use them either in the nested if statements or switch case statements. Alternatively, we can also use them as a factory of objects and strategize them to perform the related business logic.

除了使用Map,我们还可以使用Enum来标记特定的业务逻辑。之后,我们可以在嵌套的if语句switch case语句中使用它们。另外,我们也可以将它们作为对象的工厂,并对它们进行策略处理,以执行相关的业务逻辑。

That would reduce the number of nested if statements as well and delegate the responsibility to individual Enum values.

这也会减少嵌套的if语句的数量,并将责任下放给各个Enum值。

Let’s see how we can achieve it. At first, we need to define our Enum:

让我们来看看我们如何实现它。首先,我们需要定义我们的Enum

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

As we can observe, the values are the labels of the different operators which will be used further for calculation. We always have an option to use the values as different conditions in nested if statements or switch cases, but let’s design an alternate way of delegating the logic to the Enum itself.

我们可以观察到,这些值是不同运算符的标签,将被进一步用于计算。我们总是可以选择在嵌套的if语句或switch案例中使用这些值作为不同的条件,但让我们设计一种替代的方式,将逻辑委托给Enum本身。

We’ll define methods for each of the Enum values and do the calculation. For instance:

我们将为每个Enum值定义方法并进行计算。比如说。

ADD {
    @Override
    public int apply(int a, int b) {
        return a + b;
    }
},
// other operators

public abstract int apply(int a, int b);

And then in the Calculator class, we can define a method to perform the operation:

然后在Calculator类中,我们可以定义一个方法来执行该操作。

public int calculate(int a, int b, Operator operator) {
    return operator.apply(a, b);
}

Now, we can invoke the method by converting the String value to the Operator by using the Operator#valueOf() method:

现在,我们可以通过使用Operator#valueOf()方法将String值转换为Operator来调用这个方法。

@Test
public void whenCalculateUsingEnumOperator_thenReturnCorrectResult() {
    Calculator calculator = new Calculator();
    int result = calculator.calculate(3, 4, Operator.valueOf("ADD"));
    assertEquals(7, result);
}

3.3. Command Pattern

3.3.命令模式

In the previous discussion, we have seen the use of factory class to return the instance of the correct business object for the given operator. Later, the business object is used to perform the calculation in the Calculator.

在前面的讨论中,我们已经看到使用工厂类来为给定的操作者返回正确的业务对象的实例。之后,该业务对象被用于在Calculator中执行计算。

We can also design a Calculator#calculate method to accept a command which can be executed on the inputs. This will be another way of replacing nested if statements.

我们也可以设计一个Calculate#calculate方法来接受一个可以在输入端执行的命令。这将是替代嵌套if语句的另一种方式。

We’ll first define our Command interface:

我们将首先定义我们的Command接口。

public interface Command {
    Integer execute();
}

Next, let’s implement an AddCommand:

接下来,让我们实现一个AddCommand:

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;
    }
}

Finally, let’s introduce a new method in the Calculator which accepts and execute the Command:

最后,让我们在Calculator中引入一个新的方法,它接受并执行Command

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

Next, we can invoke the calculation by instantiating an AddCommand and send it to the Calculator#calculate method:

接下来,我们可以通过实例化一个AddCommand来调用计算,并将其发送给Calculator#calculate方法。

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

3.4. Rule Engine

3.4.规则引擎

When we end up writing a large number of nested if statements, each of the conditions depicts a business rule which has to be evaluated for the correct logic to be processed. A rule engine takes such complexity out of the main code. RuleEngine evaluates the Rules and returns the result based on the input.

当我们最终写下大量嵌套的if语句时,每个条件都描述了一条业务规则,必须对其进行评估,以获得正确的逻辑处理。规则引擎将这种复杂性从主代码中移除。一个规则引擎会评估规则,并根据输入返回结果。

Let’s walk through an example by designing a simple RuleEngine which processes an Expression through a set of Rules and returns the result from the selected Rule. First, we’ll define a Rule interface:

让我们通过一个例子,设计一个简单的RuleEngine,通过一组Rule来处理一个Expression,并返回所选Rule的结果。首先,我们将定义一个Rule接口。

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

Second, let’s implement a RuleEngine:

第二,让我们实现一个RuleEngine

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

The RuleEngine accepts an Expression object and returns the Result. Now, let’s design the Expression class as a group of two Integer objects with the Operator which will be applied:

RuleEngine接受一个Expression对象并返回Result。现在让我们把Expression类设计成由两个Integer对象组成的组,并设置将被应用的Operator

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

And finally let’s define a custom AddRule class which evaluates only when the ADD Operation is specified:

最后让我们定义一个自定义的AddRule类,它只在ADD Operation被指定时进行评估。

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;
    }    
}

We’ll now invoke the RuleEngine with an Expression:

我们现在要用一个表达式来调用规则引擎

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

4. Conclusion

4.结论

In this tutorial, we explored a number of different options to simplify complex code. We also learned how to replace nested if statements by the use of effective design patterns.

在本教程中,我们探讨了一些不同的选项来简化复杂的代码。我们还学习了如何通过使用有效的设计模式来取代嵌套的if语句。

As always, we can find the complete source code over the GitHub repository.

一如既往,我们可以在GitHub资源库上找到完整的源代码。

 

1.概述

1.概述

决策结构是任何编程语言的重要组成部分。但是,我们在编码中会出现大量的嵌套if语句,这使得我们的代码更加复杂,难以维护。

在本教程中,我们将通过各种方式来替换嵌套的if语句

让我们探讨一下如何简化代码的不同方案。

2.案例研究

2.案例研究

通常我们会遇到一个涉及很多条件的业务逻辑,而且每个条件都需要不同的处理。为了便于演示,让我们以Calculator类为例。

public int calculate(int a, int b, String operator) {
    int result = Integer.MIN_VALUE;

    如果("add".equals(operator)){
        结果 = a + b。
    } else if ("multiply".equals(operator)) {
        结果 = a * b;
    } else if ("divide".equals(operator)) {
        结果 = a / b;
    } else if ("subtract".equals(operator)) {
        结果 = a - b;
    }
    返回结果。
}

我们也可以使用switch语句来实现这一点

public int calculateUsingSwitch(int a, int b, String operator) {
    switch (operator) {
    case "add":
        结果= a + b。
        破解。
    // 其他情况
    }
    返回结果。
}

在典型的开发中,if 语句可能会越来越大,性质也越来越复杂。另外,当存在复杂的条件时,switch 语句并不适合

拥有嵌套决策结构的另一个副作用是它们变得难以管理。例如,如果我们需要添加一个新的操作符,我们必须添加一个新的if语句并实现该操作。

3.重构

3.重构

让我们探讨一下替代方案,将上面复杂的if语句替换成更简单、更容易管理的代码。

3.1.工厂类

3.1.工厂类

很多时候,我们遇到的决策结构最终会在每个分支中进行类似的操作。这为我们提供了一个机会提取一个工厂方法,该方法返回给定类型的对象并根据具体对象的行为执行操作

对于我们的示例,让我们定义一个操作接口,该接口有一个应用方法:

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

该方法接收两个数字作为输入并返回结果。让我们来定义一个用于执行加法的类:

public class Addition implements Operation {
@Override
public int apply(int a, int b) {
返回 a + b。
}
}

我们现在要实现一个工厂类,它可以根据给定的操作符返回操作的实例:

public class OperatorFactory {
    static Map<String, Operation> operationMap = new HashMap<>()。
    静态 {
        operationMap.put("add", new Addition())。
        operationMap.put("divide", new Division())。
        // 更多的运算符
    }

    public static Optional<Operation> getOperation(String operator) {
        return Optional.ofNullable(operatingMap.get(operator))。
    }
}

现在,在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);
}

在这个例子中,我们已经看到了如何将责任委托给由工厂类提供服务的松散耦合的对象。但是也有可能出现嵌套的 if 语句被简单地转移到工厂类中的情况,这违背了我们的目的。

另外,我们可以在Map中维护一个对象库,该对象库可被查询以进行快速查找。正如我们所见,OperatorFactory#operationMap符合我们的目的。我们也可以在运行时初始化Map,并配置它们进行查询。

3.2.枚举的使用

3.2.枚举的使用

除了使用Map,我们还可以使用Enum来标记特定的业务逻辑。之后,我们可以在嵌套的if 语句switch case语句中使用它们。另外,我们也可以将它们作为对象的工厂,并对它们进行策略,以执行相关的业务逻辑。

这也将减少嵌套的 if 语句的数量,并将责任下放给各个 Enum 值。

让我们来看看我们如何实现这一目标。首先,我们需要定义我们的Enum:

public enum Operator {
    加法、乘法、减法、除法
}

正如我们所观察到的,这些值是不同运算符的标签,将被进一步用于计算。我们总是可以选择在嵌套的if语句或switch案例中使用这些值作为不同的条件,但是让我们设计一种替代的方式,将逻辑委托给Enum本身。

我们将为每个Enum值定义方法并进行计算。例如:

ADD {
    @Override
    public int apply(int a, int b) {
        返回 a + b。
    }
},
// 其他运算符

public abstract int apply(int a, int b);

然后在Calculator类中,我们可以定义一个方法来执行这个操作:

public int calculate(int a, int b, operator operator) {
    return operator.apply(a, b);
}

现在,我们可以通过使用Operator#valueOf()方法将String值转换为Operator来调用该方法

@Test
public void whenCalculateUsingEnumOperator_thenReturnCorrectResult() {
    Calculator calculator = new Calculator();
    int result = calculator.calculate(3, 4, Operator.valueOf("ADD"))。
    assertEquals(7, result)。
}

3.3.命令模式

3.3.命令模式

在前面的讨论中,我们已经看到了使用工厂类来为给定的操作者返回正确的业务对象的实例。之后,该业务对象被用于在Calculator中执行计算。

我们还可以设计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() {
        返回 a + b;
    }
}

最后,让我们在计算器中引入一个新方法,它接受并执行Command

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

3.4.规则引擎

3.4.规则引擎

当我们最终写下大量嵌套的if语句时,每个条件都描述了一条业务规则,必须对其进行评估,以获得正确的逻辑处理。规则引擎将这种复杂性从主代码中移除。一个RuleEngine评估Rules并根据输入返回结果。

让我们通过一个例子,设计一个简单的RuleEngine,通过一组Rules来处理一个Expression,并返回所选Rule的结果。首先,我们要定义一个Rule接口:

public interface Rule {
    boolean evaluate(Expression expression);
    结果 getResult()。
}

其次,让我们实现一个RuleEngine

public class RuleEngine {
    private static List<Rule> rules = new ArrayList<> ();

    静态 {
        rules.add(new AddRule())。
    }

    public Result process(Expression expression) {
        规则规则 = rules
          .stream()
          .filter(r -> r.evaluation(expression))
          .findFirst()
          .orElseThrow(() -> new IllegalArgumentException("Expression does not matches any Rule") )。
        返回 rule.getResult()。
    }
}

RuleEngine接受一个Expression对象并返回Result。现在让我们把Expression类设计成两组Integer对象,并加上将被应用的Operator

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

最后让我们定义一个自定义的AddRule类,它只在ADD操作被指定时进行评估:

public class AddRule implements Rule {
    @Override
    public boolean evaluate(Expression expression) {
        boolean evalResult = false。
        如果(expression.getOperator() == Operator.ADD) {
            this.result = expression.getX() + expression.getY();
            evalResult = true。
        }
        返回evalResult。
    }    
}

我们现在要用一个Expression来调用RuleEngine

@Test
public void whenNumbersGivenToRuleEngine_thenReturnCorrectResult() {
    Expression expression = new Expression(5, 5, Operator.ADD);
    规则引擎 = new RuleEngine();
    结果 result = engine.process(expression);

    assertNotNull(结果)。
    assertEquals(10, result.getValue())。
}

4.结论

4.结论

在本教程中,我们探索了一些不同的选项来简化复杂的代码。我们还学习了如何通过使用有效的设计模式来取代嵌套的if语句。

一如既往,我们可以在GitHub 仓库上找到完整的源代码。