1. 序章

この記事では、Java8で戦略設計パターンを実装する方法を見ていきます。

最初に、パターンの概要を示し、古いバージョンのJavaで従来どのように実装されていたかを説明します。

次に、パターンを再試行しますが、今回はJava 8ラムダを使用して、コードの冗長性を減らします。

2. 戦略パターン

基本的に、戦略パターンにより、実行時にアルゴリズムの動作を変更できます。

通常、アルゴリズムを適用するために使用されるインターフェースから始めて、可能なアルゴリズムごとに複数回実装します。

クリスマス、イースター、正月のいずれであるかに基づいて、購入にさまざまな種類の割引を適用する必要があるとします。 まず、各戦略で実装されるディスカウンターインターフェースを作成しましょう。

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

次に、イースターに50 % dの割引を適用し、クリスマスに10% dの割引を適用するとします。 これらの戦略のそれぞれにインターフェースを実装しましょう。

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

これは非常にうまく機能しますが、問題は、戦略ごとに具体的なクラスを作成しなければならないのが少し面倒な場合があることです。 別の方法は匿名の内部型を使用することですが、それでもかなり冗長であり、以前のソリューションよりもそれほど便利ではありません。

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

3. Java8の活用

Java 8がリリースされて以来、ラムダの導入により、匿名内部型が多かれ少なかれ冗長になりました。 つまり、戦略を一列に作成することが、はるかにクリーンで簡単になりました。

さらに、関数型プログラミングの宣言型スタイルにより、以前は不可能だったパターンを実装できます。

3.1. コードの冗長性を減らす

今回はラムダ式を使用して、インライン EasterDiscounter、のみを作成してみましょう。

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

ご覧のとおり、コードはよりクリーンで保守しやすくなり、以前と同じですが1行で実行できます。 基本的に、ラムダは匿名の内部タイプの代わりと見なすことができます。

この利点は、さらに多くの割引をインラインで宣言する場合にさらに明らかになります。

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

多くの割引を定義したい場合、それらをすべて1か所で静的に宣言できます。 Java 8では、必要に応じて、インターフェースで静的メソッドを定義することもできます。

したがって、具象クラスまたは匿名内部型のどちらかを選択する代わりに、すべて1つのクラスでラムダを作成してみましょう。

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. 機能構成の活用

Discount インターフェースを変更して、 UnaryOperator インターフェースを拡張してから、 combine()メソッドを追加しましょう。

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

基本的に、リファクタリングを行っていますディスカウントストア割引を適用することは、 BigDecimal 別のインスタンスに BigDecimal 実例 事前定義されたメソッドにアクセスできるようにするUnaryOperatorにはapply()メソッドが付属しているため、applyDiscountをそれに置き換えることができます。

combine()メソッドは、1つの Discountthisの結果に適用することを抽象化したものです。組み込みの関数apply()を使用しますこれを実現するために。

それでは、複数の割引を累積的に適用してみましょう。 これを行うには、機能的な reduce() combine():を使用します。

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

combinedDiscounter.apply(...);

最初のreduce引数に特に注意してください。 割引が提供されていない場合は、変更されていない値を返す必要があります。 これは、デフォルトのディスカウンターとして恒等関数を提供することで実現できます。

これは、標準の反復を実行するよりも便利で冗長性の低い代替手段です。 箱から出して機能合成を行う方法を考えると、より多くの機能が無料で提供されます。

4. 結論

この記事では、戦略パターンについて説明し、ラムダ式を使用して、より冗長でない方法でそれを実装する方法も示しました。

これらの例の実装は、GitHubにあります。 これはMavenベースのプロジェクトであるため、そのまま実行するのは簡単です。