1. 概述
在这篇文章中,我们将探讨如何在Java 8中实现策略模式。
首先,我们会概述这个设计模式,并解释在旧版本Java中如何传统地实现它。
接下来,我们将使用Java 8的lambda表达式再次尝试该模式,以减少代码的冗余。
2. 策略模式
策略模式本质上允许我们在运行时改变算法的行为。
通常,我们首先会定义一个接口,用于应用算法,然后针对每种可能的算法多次实现该接口。
假设我们有一个需求,根据购买是否是圣诞节、复活节或新年,对购买应用不同类型的折扣。首先,让我们创建一个Discounter
接口,每个策略都将实现它:
public interface Discounter {
BigDecimal applyDiscount(BigDecimal amount);
}
假设我们想在复活节时应用50%的折扣,在圣诞节时应用10%的折扣。那么,让我们为这些策略分别实现我们的接口:
public static class EasterDiscounter implements Discounter {
@Override
public BigDecimal applyDiscount(final BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.5));
}
}
public static class ChristmasDiscounter implements Discounter {
@Override
public BigDecimal applyDiscount(final BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.9));
}
}
最后,让我们在一个测试中尝试一种策略:
Discounter easterDiscounter = new EasterDiscounter();
BigDecimal discountedValue = easterDiscounter
.applyDiscount(BigDecimal.valueOf(100));
assertThat(discountedValue)
.isEqualByComparingTo(BigDecimal.valueOf(50));
这种方法工作得很好,但问题在于每次需要为每个策略创建一个具体类,这可能会有些繁琐。另一种选择是使用匿名内部类,但这仍然相当冗长,而且不如前一种解决方案方便。
3. 利用Java 8
自从Java 8发布以来,lambda表达式的引入使得匿名内部类型几乎变得多余。这意味着现在在线创建策略更加简洁且易于维护。
此外,函数式编程的声明式风格使我们能够实现以前无法实现的模式。
3.1. 减少代码冗余
让我们尝试创建一个线上的EasterDiscounter
,这次使用lambda表达式:
Discounter easterDiscounter = amount -> amount.multiply(BigDecimal.valueOf(0.5));
如我们所见,我们的代码现在更清晰,更易于维护,与之前的效果相同,但只需要一行代码。基本上,lambda可以被视为匿名内部类型的替代品。
当我们想要在线声明更多的Discounters
时,这种优势更为明显:
List<Discounter> discounters = newArrayList(
amount -> amount.multiply(BigDecimal.valueOf(0.9)),
amount -> amount.multiply(BigDecimal.valueOf(0.8)),
amount -> amount.multiply(BigDecimal.valueOf(0.5))
);
当我们要定义大量Discounters
时,我们可以一次性在同一个地方静态地声明它们。Java 8甚至允许我们在接口中定义静态方法。
因此,我们可以尝试在一个单独的类中创建所有的lambda表达式:
public interface Discounter {
BigDecimal applyDiscount(BigDecimal amount);
static Discounter christmasDiscounter() {
return amount -> amount.multiply(BigDecimal.valueOf(0.9));
}
static Discounter newYearDiscounter() {
return amount -> amount.multiply(BigDecimal.valueOf(0.8));
}
static Discounter easterDiscounter() {
return amount -> amount.multiply(BigDecimal.valueOf(0.5));
}
}
如你所见,我们用不多的代码实现了很多功能。
3.2. 利用函数组合
让我们修改Discounter
接口,使其扩展UnaryOperator
接口,并添加一个combine()
方法:
public interface Discounter extends UnaryOperator<BigDecimal> {
default Discounter combine(Discounter after) {
return value -> after.apply(this.apply(value));
}
}
实质上,我们正在重构Discounter
,并利用一个事实:应用折扣是一个将BigDecimal
实例转换为另一个BigDecimal
实例的函数,这样我们就可以访问预定义的方法。由于UnaryOperator
接口带有apply()
方法,我们可以直接替换applyDiscount
。
combine()
方法只是一个抽象,用于将this
的结果应用到另一个Discounter
上。它使用内置的函数式apply()
来实现这一点。
现在,让我们尝试累计应用多个Discounters
到一个金额上。我们将使用函数式reduce()
和combine()
来实现:
Discounter combinedDiscounter = discounters
.stream()
.reduce(v -> v, Discounter::combine);
combinedDiscounter.apply(...);
请注意第一个reduce
参数。如果没有提供折扣,我们需要返回不改变的值。这可以通过提供一个身份函数作为默认折扣器来实现。
这是一种有用且不太冗余的替代标准迭代的方式。考虑到我们从箱子里得到的函数组合方法,它还为我们提供了许多免费的功能。
4. 总结
在这篇文章中,我们解释了策略模式,并展示了如何使用lambda表达式以更简洁的方式实现它。
这些示例的实现可以在GitHub上找到。这是一个基于Maven的项目,所以可以直接运行。