本文へスキップ
BecomeCoder

Javaデザインパターンコース · 第4章 振る舞いに関するパターン ― オブジェクト同士の「連携の仕方」を工夫する · レッスン9

Strategy ― アルゴリズムを丸ごと差し替える

ブラウザで完結

導入

「通常価格」「10%オフ」「会員はさらに固定額オフ」……割引の計算方法が増えるたびに if/else if を継ぎ足していくと、どんどん巨大な分岐の塊になります。Strategy(ストラテジー) は、アルゴリズム(処理の手順)そのものをインタフェースとして切り出し、丸ごと差し替えられるようにする パターンです。第1章のSOLIDで見た「依存性逆転」の考え方の具体例でもあります。

説明

「割引の計算方法」を DiscountStrategy というインタフェースに切り出し、Cart はその具体的な中身を知らずに呼び出すだけにします。

public class Main {
    public static void main(String[] args) {
        Cart cart = new Cart(new PercentDiscount(10));
        System.out.println(cart.checkout(1000));

        cart.setStrategy(new NoDiscount());
        System.out.println(cart.checkout(1000));
    }
}

interface DiscountStrategy {
    int apply(int price);
}

class NoDiscount implements DiscountStrategy {
    public int apply(int price) {
        return price;
    }
}

class PercentDiscount implements DiscountStrategy {
    private int percent;

    PercentDiscount(int percent) {
        this.percent = percent;
    }

    public int apply(int price) {
        return price - price * percent / 100;
    }
}

class Cart {
    private DiscountStrategy strategy;

    Cart(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    int checkout(int price) {
        return strategy.apply(price);
    }
}
classDiagram
  DiscountStrategy <|.. NoDiscount
  DiscountStrategy <|.. PercentDiscount
  Cart --> DiscountStrategy : uses
  class DiscountStrategy {
    <<interface>>
    +apply(price) int
  }
  • CartDiscountStrategy strategy; というフィールドを持ちますが、それが NoDiscount なのか PercentDiscount なのかを一切気にしません。
  • cart.checkout(1000) は、そのとき差し込まれている strategyapply を呼ぶだけ。割引の計算ロジックは Cart の外側に出ています
  • cart.setStrategy(new NoDiscount()) … 実行の途中でアルゴリズムを丸ごと差し替えられます。Cart 自体のコードは一切変更していません。
  • 新しい割引方法(例えば「会員は固定200円引き」)を追加したいときも、DiscountStrategy を実装した新しいクラスを1つ足すだけで済みます。

やってみよう

下のエディタを実行し、PercentDiscount(10) で1000円が900円になること、setStrategy(new NoDiscount()) に切り替えた後は1000円のままになることを確認しましょう。

演習

FixedDiscount という DiscountStrategy を作ってください。コンストラクタで割引額(int amount)を受け取り、apply(price)price - amount を返します。mainCart cart = new Cart(new FixedDiscount(200)); を作り、cart.checkout(1000) を表示してください。

ヒント1を見る

class FixedDiscount implements DiscountStrategy { private int amount; FixedDiscount(int amount) { this.amount = amount; } public int apply(int price) { return price - amount; } }

ヒント2を見る

Cart はコンストラクタで最初の strategy を受け取れるので、new Cart(new FixedDiscount(200)) とそのまま渡せます。

実際に動かしてみよう

下のエディタにJavaを書いて「実行」を押すと、学習用シミュレータが System.out.println の出力を表示します(本物のJVMではなく、教材の範囲を再現した軽量エンジンです)。クラス・継承・コレクションなども動きます。Scanner で入力を読む例は「標準入力」欄に値を入れてください。本文の例を書き換えて動かしてみましょう(Stream・ラムダ・スレッド・ファイル入出力など一部の機能は対象外です)。

Java — ブラウザ内で実行(学習用シミュレータ)

Javaの教材サブセットを動かす学習用シミュレータを読み込みます(本物のJVMではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。