1前書き

この記事では、Java 8でストラテジー設計パターンを実装する方法について説明します。

まず、パターンの概要を説明し、それが以前のバージョンのJavaでどのように伝統的に実装されてきたかを説明します。

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


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

これは非常にうまく機能しますが、問題は、各戦略に対して具体的なクラスを作成しなければならないことが少し面倒になることです。代わりの方法は匿名の内部型を使うことですが、それはまだかなり冗長であり、以前の解決策ほどは便利ではありません。

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


3 Java 8

を活用する

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

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

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

今回はラムダ式を使用して__EasterDiscounterを作成してみましょう。

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

お分かりのように、私たちのコードは今でははるかにクリーンで保守性がよくなり、以前と同じように、ただし単一行で達成できます。基本的に、

ラムダは匿名の内部型

の置き換えとして見なすことができます。

この利点は、さらに

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では、必要に応じてインターフェース内に静的メソッドを定義することさえできます。

それで、具象クラスか匿名の内部型のどちらかを選ぶ代わりに、ひとつのクラスの中にすべてラムダを作成してみましょう:

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

インスタンスに変換する関数であるという事実を利用しています。

applyDiscount

をそれに置き換えるだけです。**


combine()メソッドは、これを実現するために、

Disounter



this.の結果に適用することを抽象化したものです。

それでは、複数の

Discounters

を累積的に金額に適用してみましょう。これを機能的な

reduce()

と__combine()を使用して行います。

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

combinedDiscounter.apply(...);

最初の

reduce

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

これは、標準的な反復を実行することに代わる便利で冗長な方法です。私たちが機能構成のために箱から出して出ている方法を考えるならば、それはまた私たちに無料でもっと多くの機能を与えます。


4結論

この記事では、ストラテジー・パターンについて説明し、それをあまり冗長ではない方法で実装するためにラムダ式を使用する方法についても説明しました。

これらの例の実装はhttps://github.com/eugenp/tutorials/tree/master/core-java-8[over GitHub]で見つけることができます。これはMavenベースのプロジェクトなので、そのまま実行するのは簡単なはずです。