1. 概述

在使用if语句时,我们可能需要在其内部使用逻辑运算符(如AND或OR)处理多个条件。这可能不是一种干净的设计,会影响代码的可读性和认知复杂度。

在这篇教程中,我们将探讨如何在if语句中格式化多个值条件的替代方法。

2. 我们能避免if语句吗?

假设我们有一个电子商务平台,并为特定月份出生的人设置折扣。让我们看一个代码片段:

if (month == 10 || month == 11) {
    // doSomething()
} else if(month == 4 || month == 5) {
    // doSomething2()
} else {
    // doSomething3()
}

这可能会导致难以阅读的代码。即使测试覆盖率良好,随着条件的增加,维护起来也可能变得困难,例如,我们需要将不同的条件组合在一起才能执行特定操作。

2.1. 使用整洁的代码

我们可以应用模式来替换多个if语句。例如,我们可以将if中多个条件的逻辑移到类或枚举中。在运行时,根据客户端输入切换接口。类似地,可以考虑使用策略模式

这并不直接关系到格式化,通常会促使我们重新思考逻辑。不过,这是我们可以考虑的一种设计改进方法。

2.2. 提升方法语法

只要代码易于阅读和维护,使用if/else逻辑并没有错。例如,考虑以下代码片段:

if (month == 8 || month == 9) {
    return doSomething();
} else {
    return doSomethingElse();
}

首先,我们可以删除else部分:

if (month == 8 || month == 9) {
    return doSomething();
}

return doSomethingElse();

此外,其他代码也可以改进,例如,用java.time包中的枚举替换月份数字:

if (month == OCTOBER || month == NOVEMBER || month == DECEMBER) {
    return doSomething();
}
// ...

这些都是简单但有效的代码优化。因此,在应用复杂模式之前,我们应该先确保代码的可读性。

我们还将学习如何使用函数式编程。在Java中,从8版开始,可以使用lambda表达式语法实现这一点。

3. 测试说明

以电子商务折扣示例为基础,我们将创建测试并检查折扣月份内的值。例如,十月到十二月。否则我们将断言为假。我们将设置随机的月份,可能是允许的,也可能是不允许的:

Month monthIn() {
    return Month.of(rand.ints(10, 13)
      .findFirst()
      .orElse(10));
}

Month monthNotIn() {
    return Month.of(rand.ints(1, 10)
      .findFirst()
      .orElse(1));
}

尽管可能存在多个if条件,为了简化,我们将假设只有一个if/else语句。

4. 使用switch

使用if逻辑的一个替代方案是switch语句。让我们看看如何在我们的例子中使用它:

boolean switchMonth(Month month) {
    switch (month) {
        case OCTOBER:
        case NOVEMBER:
        case DECEMBER:
            return true;
        default:
            return false;
    }
}

请注意,如果需要,它会向下遍历并检查所有有效月份。此外,我们可以使用Java 12的新switch语法进行改进:

return switch (month) {
    case OCTOBER, NOVEMBER, DECEMBER -> true;
    default -> false;
};

最后,我们可以做一些测试来验证值是否在范围内:

assertTrue(switchMonth(monthIn()));
assertFalse(switchMonth(monthNotIn()));

5. 使用集合

我们可以使用集合来分组满足if条件的元素,并检查某个值是否属于其中

Set<Month> months = Set.of(OCTOBER, NOVEMBER, DECEMBER);

让我们添加一些逻辑来查看集合是否包含特定值:

boolean contains(Month month) {
    if (months.contains(month)) {
        return true;
    }
    return false;
}

同样,我们可以添加单元测试:

assertTrue(contains(monthIn()));
assertFalse(contains(monthNotIn()));

6. 使用函数式编程

我们可以使用函数式编程将if/else逻辑转换为函数。这样,我们的方法调用方式就具有可预测性。

6.1. 简单谓词

我们仍然使用contains()方法,但这次将其转换为一个使用Predicate的lambda表达式:

Predicate<Month> collectionPredicate = this::contains;

现在,我们确信Predicate是不可变的,没有中间变量。其结果是可预测的,如果需要,可以在其他上下文中重用。

让我们用test()方法来验证它:

assertTrue(collectionPredicate.test(monthIn()));
assertFalse(collectionPredicate.test(monthNotIn()));

6.2. 谓词链

我们可以将多个Predicate连接起来,以实现or条件:

Predicate<Month> orPredicate() {
    Predicate<Month> predicate = x -> x == OCTOBER;
    Predicate<Month> predicate1 = x -> x == NOVEMBER;
    Predicate<Month> predicate2 = x -> x == DECEMBER;

    return predicate.or(predicate1).or(predicate2);
}

然后将其插入if中:

boolean predicateWithIf(Month month) {
    if (orPredicate().test(month)) {
        return true;
    }
    return false;
}

让我们通过测试确认这是否有效:

assertTrue(predicateWithIf(monthIn()));
assertFalse(predicateWithIf(monthNotIn()));

6.3. 谓词在流中

类似地,我们可以在流过滤中使用Predicate。Lambda表达式将替代并增强if逻辑。最终,if将消失。这是函数式编程的优点,同时保持良好的性能和可读性。

让我们在解析输入月份列表时进行测试:

List<Month> monthList = List.of(monthIn(), monthIn(), monthNotIn());

monthList.stream()
  .filter(this::contains)
  .forEach(m -> assertThat(m, is(in(months))));

我们也可以使用predicateWithIf()而不是contains().*。如果支持方法签名,lambda没有限制,例如,它可以是静态方法。

7. 总结

在这篇教程中,我们学习了如何提高if语句中多个条件的可读性。我们看到了如何使用switch作为替代。此外,我们还了解了如何使用集合检查值是否包含。最后,我们了解了如何采用函数式编程方法,使用lambda表达式。PredicateStream减少了错误的可能性,提高了代码的可读性和维护性。

如往常一样,本文档中的代码可在GitHub上的教程仓库中找到。