如何在Java中替换多个if语句

2023/05/26

1. 概述

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

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

2. 案例研究

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

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;
	} else if ("modulo".equals(operator)) {
		result = a % b;
	}
	return result;
}

我们也可以使用switch语句来实现:

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

在典型的开发中,if语句在本质上可能会变得更大更复杂。此外,当存在复杂条件时,switch语句也不适合

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

3. 重构

3.1 工厂类

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

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

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

该方法将两个数字作为输入并返回结果,现在我们定义一个类来执行加法:

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

然后我们实现一个工厂类,该类根据给定的运算符返回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));
    }
}

现在,在Calculator中,我们可以查询工厂以获取相关操作并应用于源数字:

public int calculateUsingFactory(int a, int b, String operation) {
	Operation targetOperation = OperatorFactory
		.getOperation(operation)
		.orElseThrow(() -> new IllegalArgumentException("Invalid Operator"));
	return targetOperation.apply(a, b);
}

在这个例子中,我们看到了责任是如何委托给工厂类服务的松耦合对象的。但是嵌套的if语句可能会被简单地转移到工厂类,这违背了我们的目的。

或者,我们可以在Map中维护一个对象存储库,可以查询该存储库以进行快速查找。正如我们所见,OperatorFactory#operationMap服务于我们的目的,我们还可以在运行时初始化Map并配置它们以进行查找。

3.2 枚举的使用

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

这也可以减少嵌套if语句的数量,并将责任委托给各个枚举值。

让我们看看如何实现它。首先,我们需要定义我们的枚举:

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

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

我们为每个枚举值定义方法并进行计算,例如:

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

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
void whenCalculateUsingEnumOperator_thenReturnCorrectResult() {
    Calculator calculator = new Calculator();
    int result = calculator.calculate(3, 4, Operator.valueOf("ADD"));
    assertEquals(7, result);
}

3.3 命令模式

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

我们还可以设计一个Calculator#calculate方法来接收可以在输入上执行的命令,这是替换嵌套if语句的另一种方式

首先定义我们的命令接口:

public interface Command {
    Integer execute();
}

接下来,我们实现一个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;
    }
}

最后,我们在Calculator中引入一个新方法,它接收并执行命令:

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

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

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

3.4 规则引擎

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

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

public interface Rule {
    boolean evaluate(Expression expression);

    Result getResult();
}

其次,我们实现一个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();
    }
}

RuleEngine接收一个Expression对象并返回Result。现在,让我们将Expression类设计为一组两个Integer对象,并使用将要应用的运算符:

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;
        if (expression.getOperator() == Operator.ADD) {
            this.result = expression.getX() + expression.getY();
            evalResult = true;
        }
        return evalResult;
    }
}

现在,我们可以使用Expression调用RuleEngine:

@Test
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. 总结

在本教程中,我们介绍了许多不同的方式来简化复杂的代码,并学习了如何使用有效的设计模式替换嵌套的if语句。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章