Docs 一覧
Java Design Patterns #java #design-patterns #strategy #oop

Javaデザインパターン:Strategyパターン完全解説

GoFの行動パターンの一つであるStrategyパターンを、Javaのコードサンプルとともに詳しく解説します。

Strategyパターンとは

Strategyパターンは、アルゴリズムのファミリを定義し、それぞれをカプセル化して交換可能にするデザインパターンです。 GoF(Gang of Four)の行動パターン(Behavioral Pattern)に分類されます。

「アルゴリズムを使うクライアントから独立して、アルゴリズムを変えられるようにする。」— GoF

問題:if-else地獄

Strategyパターンを使わない場合、こんなコードになりがちです:

// アンチパターン: if-else地獄
public class PaymentProcessor {
    public void processPayment(String paymentType, double amount) {
        if (paymentType.equals("CREDIT_CARD")) {
            // クレジットカード処理
            System.out.println("クレジットカードで " + amount + "円 支払い");
        } else if (paymentType.equals("PAYPAL")) {
            // PayPal処理
            System.out.println("PayPalで " + amount + "円 支払い");
        } else if (paymentType.equals("CRYPTO")) {
            // 仮想通貨処理
            System.out.println("仮想通貨で " + amount + "円 支払い");
        }
        // 新しい支払い方法が増えるたびにここを変更する必要がある...
    }
}

新しい支払い方法が追加されるたびに、このクラスを変更しなければなりません。開放閉鎖原則(OCP)に違反しています。

解決:Strategyパターンの実装

Step 1: Strategy インターフェースを定義

// Strategy インターフェース
public interface PaymentStrategy {
    void pay(double amount);
    String getPaymentMethodName();
}

Step 2: 具体的な Strategy を実装

// Concrete Strategy 1: クレジットカード
public class CreditCardStrategy implements PaymentStrategy {
    private final String cardNumber;
    private final String cardHolder;

    public CreditCardStrategy(String cardNumber, String cardHolder) {
        this.cardNumber = cardNumber;
        this.cardHolder = cardHolder;
    }

    @Override
    public void pay(double amount) {
        System.out.printf("クレジットカード [%s] で %.0f円 支払い%n",
            maskCardNumber(cardNumber), amount);
    }

    @Override
    public String getPaymentMethodName() {
        return "Credit Card";
    }

    private String maskCardNumber(String number) {
        return "**** **** **** " + number.substring(number.length() - 4);
    }
}
// Concrete Strategy 2: PayPal
public class PayPalStrategy implements PaymentStrategy {
    private final String email;

    public PayPalStrategy(String email) {
        this.email = email;
    }

    @Override
    public void pay(double amount) {
        System.out.printf("PayPal [%s] で %.0f円 支払い%n", email, amount);
    }

    @Override
    public String getPaymentMethodName() {
        return "PayPal";
    }
}
// Concrete Strategy 3: 仮想通貨
public class CryptoStrategy implements PaymentStrategy {
    private final String walletAddress;

    public CryptoStrategy(String walletAddress) {
        this.walletAddress = walletAddress;
    }

    @Override
    public void pay(double amount) {
        System.out.printf("仮想通貨 [%s...] で %.0f円 相当 支払い%n",
            walletAddress.substring(0, 8), amount);
    }

    @Override
    public String getPaymentMethodName() {
        return "Crypto";
    }
}

Step 3: Context クラス(Strategy を使う側)

// Context クラス
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    private final List<Item> items = new ArrayList<>();

    // Strategy を注入(依存性の注入)
    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void addItem(Item item) {
        items.add(item);
    }

    public void checkout() {
        if (paymentStrategy == null) {
            throw new IllegalStateException("支払い方法が設定されていません");
        }
        double total = items.stream()
            .mapToDouble(Item::getPrice)
            .sum();

        System.out.println("=== 注文確認 ===");
        System.out.println("合計: " + total + "");
        System.out.println("支払い方法: " + paymentStrategy.getPaymentMethodName());

        paymentStrategy.pay(total);
        items.clear();
    }
}

Step 4: 実際の使用例

public class Main {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        cart.addItem(new Item("Javaプログラミング入門", 3000));
        cart.addItem(new Item("デザインパターン解説本", 4500));

        // クレジットカードで支払い
        cart.setPaymentStrategy(
            new CreditCardStrategy("1234567890123456", "Zhong Tao")
        );
        cart.checkout();

        // 注文2件目: PayPalで支払い(戦略を切り替え)
        cart.addItem(new Item("アジャイル実践ガイド", 2800));
        cart.setPaymentStrategy(new PayPalStrategy("zhongtao@example.com"));
        cart.checkout();
    }
}

出力結果:

=== 注文確認 ===
合計: 7500円
支払い方法: Credit Card
クレジットカード [**** **** **** 3456] で 7500円 支払い
=== 注文確認 ===
合計: 2800円
支払い方法: PayPal
PayPal [zhongtao@example.com] で 2800円 支払い

UML クラス図

┌─────────────────┐        ┌──────────────────────┐
│   ShoppingCart  │───────▶│   PaymentStrategy    │
│   (Context)     │ uses   │   <>       │
├─────────────────┤        ├──────────────────────┤
│ -strategy       │        │ +pay(amount)         │
│ -items          │        │ +getPaymentMethodName│
├─────────────────┤        └──────────┬───────────┘
│ +setStrategy()  │                   │ implements
│ +checkout()     │         ┌─────────┼─────────┐
└─────────────────┘         ▼         ▼         ▼
                    ┌──────────┐ ┌────────┐ ┌────────┐
                    │CreditCard│ │PayPal  │ │Crypto  │
                    │Strategy  │ │Strategy│ │Strategy│
                    └──────────┘ └────────┘ └────────┘

Strategyパターンのメリット・デメリット

メリット デメリット
OCP準拠:新しいStrategyを追加してもContextを変更不要 Strategyクラスが多くなる可能性がある
単一責任:各Strategyは1つのアルゴリズムのみ実装 クライアントが全Strategyを知る必要がある
テストしやすい:各Strategyを独立してテスト可能 シンプルなケースではオーバーエンジニアリングになることも
実行時に動的に戦略を切り替えられる

実際のJavaライブラリでの使用例

  • java.util.Comparator — ソートアルゴリズムのStrategy
  • javax.servlet.http.HttpServlet — HTTPメソッドごとの処理がStrategy
  • Spring Security の AuthenticationStrategy

まとめ

Strategyパターンは、「振る舞いを変えたい」ときに最初に思い出すべきパターンです。 if-else地獄を解消し、Open/Closed Principleを守りながら柔軟なコードを実現できます。 Javaの関数型インターフェース(@FunctionalInterface)と組み合わせることで、 さらにシンプルに実装することも可能です。