Strategy Design Pattern in Java 8 – Java 8中的策略设计模式

最后修改: 2017年 2月 9日

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

1. Introduction

1.介绍

In this article, we’ll look at how we can implement the strategy design pattern in Java 8.

在这篇文章中,我们将看看如何在Java 8中实现策略设计模式。

First, we’ll give an overview of the pattern, and explain how it’s been traditionally implemented in older versions of Java.

首先,我们将对该模式进行概述,并解释它在旧版本的Java中的传统实现方式。

Next, we’ll try out the pattern again, only this time with Java 8 lambdas, reducing the verbosity of our code.

接下来,我们将再次尝试这个模式,只是这次使用了Java 8的lambdas,减少了我们代码的冗长性。

2. Strategy Pattern

2.战略模式

Essentially, the strategy pattern allows us to change the behavior of an algorithm at runtime.

本质上,策略模式允许我们在运行时改变算法的行为。

Typically, we would start with an interface which is used to apply an algorithm, and then implement it multiple times for each possible algorithm.

通常情况下,我们会从一个用于应用算法的接口开始,然后为每个可能的算法多次实现它。

Let’s say we have a requirement to apply different types of discounts to a purchase, based on whether it’s a Christmas, Easter or New Year. First, let’s create a Discounter interface which will be implemented by each of our strategies:

假设我们有一个要求,根据是圣诞节、复活节还是新年,对购买的商品适用不同类型的折扣。首先,让我们创建一个Discounter界面,它将由我们的每个策略来实现。

public interface Discounter {
    BigDecimal applyDiscount(BigDecimal amount);
}

Then let’s say we want to apply a 50% discount at Easter and a 10% discount at Christmas. Let’s implement our interface for each of these strategies:

然后让我们说,我们想在复活节实行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));
   }
}

Finally, let’s try a strategy in a test:

最后,让我们在测试中尝试一种策略。

Discounter easterDiscounter = new EasterDiscounter();

BigDecimal discountedValue = easterDiscounter
  .applyDiscount(BigDecimal.valueOf(100));

assertThat(discountedValue)
  .isEqualByComparingTo(BigDecimal.valueOf(50));

This works quite well, but the problem is it can be a little bit of a pain to have to create a concrete class for each strategy. The alternative would be to use anonymous inner types, but that’s still quite verbose and not much handier than the previous solution:

这样做效果很好,但问题是要为每个策略创建一个具体的类,会有点麻烦。另一个办法是使用匿名的内部类型,但这仍然很繁琐,而且也不比之前的方案方便。

Discounter easterDiscounter = new Discounter() {
    @Override
    public BigDecimal applyDiscount(final BigDecimal amount) {
        return amount.multiply(BigDecimal.valueOf(0.5));
    }
};

3. Leveraging Java 8

3.利用Java 8

Since Java 8 has been released, the introduction of lambdas has made anonymous inner types more or less redundant. That means creating strategies in line is now a lot cleaner and easier.

自从Java 8发布以来,lambdas的引入使得匿名的内部类型或多或少变得多余了。这意味着在行内创建策略现在变得更加干净和容易。

Furthermore, the declarative style of functional programming lets us implement patterns that were not possible before.

此外,函数式编程的声明性风格让我们可以实现以前不可能实现的模式。

3.1. Reducing Code Verbosity

3.1.减少代码中的言词

Let’s try creating an inline EasterDiscounter, only this time using a lambda expression:

让我们试着创建一个内联EasterDiscounter,只是这次使用了一个lambda表达式。

Discounter easterDiscounter = amount -> amount.multiply(BigDecimal.valueOf(0.5));

As we can see, our code is now a lot cleaner and more maintainable, achieving the same as before but in a single line. Essentially, a lambda can be seen as a replacement for an anonymous inner type.

正如我们所看到的,我们的代码现在干净多了,也更容易维护了,实现了和以前一样的效果,但只用了一行。从本质上讲,lambda可以被看作是匿名内部类型的替代品

This advantage becomes more apparent when we want to declare even more Discounters in line:

当我们想在生产线上申报更多的折扣机时,这一优势就变得更加明显。

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

When we want to define lots of Discounters, we can declare them statically all in one place. Java 8 even lets us define static methods in interfaces if we want to.

当我们想定义大量的Discounters时,我们可以在一个地方静态地声明它们。如果我们愿意,Java 8甚至允许我们在接口中定义静态方法。

So instead of choosing between concrete classes or anonymous inner types, let’s try creating lambdas all in a single class:

因此,与其在具体的类或匿名的内部类型之间选择,不如尝试在一个单一的类中全部创建lambdas。

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

As we can see, we are achieving a lot in a not very much code.

正如我们所看到的,我们在不多的代码中实现了很多。

3.2. Leveraging Function Composition

3.2.利用函数组合

Let’s modify our Discounter interface so it extends the UnaryOperator interface, and then add a combine() method:

让我们修改我们的Discounter接口,使其扩展UnaryOperator接口,然后添加一个combine() 方法。

public interface Discounter extends UnaryOperator<BigDecimal> {
    default Discounter combine(Discounter after) {
        return value -> after.apply(this.apply(value));
    }
}

Essentially, we are refactoring our Discounter and leveraging a fact that applying a discount is a function that converts a BigDecimal instance into another BigDecimal instance, allowing us to access predefined methods. As the UnaryOperator comes with an apply() method, we can just replace applyDiscount with it.

本质上,我们正在重构我们的Discounter,并利用一个事实,即应用折扣是一个将BigDecimal实例转换为另一个BigDecimal实例的函数允许我们访问预定义方法由于UnaryOperator带有apply()方法,我们可以直接用它来替换applyDiscount

The combine() method is just an abstraction around applying one Discounter to the results of this. It uses the built-in functional apply() in order to achieve this.

combine()方法只是围绕将一个Discounter应用到this.的结果的一个抽象。它使用内置的函数apply() 来实现这一目的。

Now, Let’s try applying multiple Discounters cumulatively to an amount. We will do this by using the functional reduce() and our combine():

现在,让我们尝试将多个折扣累积应用于一个金额。我们将通过使用函数reduce()和我们的combine()来做到这一点:

Discounter combinedDiscounter = discounters
  .stream()
  .reduce(v -> v, Discounter::combine);

combinedDiscounter.apply(...);

Pay special attention to the first reduce argument. When no discounts provided, we need to return the unchanged value. This can be achieved by providing an identity function as the default discounter.

请特别注意第一个reduce参数。当没有提供折扣时,我们需要返回未改变的值。这可以通过提供一个身份函数作为默认折扣器来实现。

This is a useful and less verbose alternative to performing a standard iteration. If we consider the methods we are getting out of the box for functional composition, it also gives us a lot more functionality for free.

这是一个有用的、不那么冗长的替代执行标准迭代的方法。如果我们考虑到我们在函数式组合中得到的方法,它还免费为我们提供了很多功能。

4. Conclusion

4.结论

In this article, we’ve explained the strategy pattern, and also demonstrated how we can use lambda expressions to implement it in a way which is less verbose.

在这篇文章中,我们解释了策略模式,还演示了如何使用lambda表达式以一种不那么冗长的方式来实现它。

The implementation of these examples can be found over on GitHub. This is a Maven based project, so should be easy to run as is.

这些例子的实现可以在GitHub上找到over。这是一个基于Maven的项目,所以应该很容易按原样运行。