Strategy パターン

分類: 振る舞いパターン

目的: アルゴリズムのファミリーを定義し、それぞれをカプセル化して交換可能にします。クライアントに影響を与えずにアルゴリズムを変更できます。

概要

Strategy パターンは、同じ問題を解決するための異なるアルゴリズムをカプセル化し、実行時に選択可能にするパターンです。このパターンを使用すると、アルゴリズムを使用するクライアントから、アルゴリズムの実装詳細を分離することができます。

クラス図

図1: Strategy パターンのクラス図

主要なコンポーネント

  • Strategy(戦略):すべての具体的な戦略に共通のインターフェースを定義します。
  • ConcreteStrategy(具体的戦略):Strategy インターフェースを実装し、特定のアルゴリズムを提供します。
  • Context(コンテキスト):Strategy オブジェクトへの参照を保持し、Strategy を利用して処理を行います。

シーケンス図

図2: Strategy パターンのシーケンス図

実装例

Strategy インターフェース

public interface PaymentStrategy {
    boolean pay(double amount);
}

ConcreteStrategy クラス

public class CreditCardPayment implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String expiryDate;
    
    public CreditCardPayment(String name, String cardNumber, String cvv, String expiryDate) {
        this.name = name;
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.expiryDate = expiryDate;
    }
    
    @Override
    public boolean pay(double amount) {
        System.out.println(amount + "円をクレジットカードで支払いました");
        // クレジットカード支払いのロジック
        return true;
    }
}
public class PayPalPayment implements PaymentStrategy {
    private String email;
    private String password;
    
    public PayPalPayment(String email, String password) {
        this.email = email;
        this.password = password;
    }
    
    @Override
    public boolean pay(double amount) {
        System.out.println(amount + "円をPayPalで支払いました");
        // PayPal支払いのロジック
        return true;
    }
}

Context クラス

public class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    private List<Item> items;
    
    public ShoppingCart() {
        this.items = new ArrayList<>();
    }
    
    public void addItem(Item item) {
        items.add(item);
    }
    
    public void removeItem(Item item) {
        items.remove(item);
    }
    
    public double calculateTotal() {
        return items.stream().mapToDouble(Item::getPrice).sum();
    }
    
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }
    
    public boolean checkout() {
        double total = calculateTotal();
        return paymentStrategy.pay(total);
    }
}

Item クラス

public class Item {
    private String name;
    private double price;
    
    public Item(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    public String getName() {
        return name;
    }
    
    public double getPrice() {
        return price;
    }
}

クライアント

public class ShoppingExample {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();
        
        // 商品をカートに追加
        cart.addItem(new Item("ノートパソコン", 80000));
        cart.addItem(new Item("ヘッドフォン", 5000));
        
        // クレジットカード支払い
        cart.setPaymentStrategy(new CreditCardPayment(
            "山田太郎", "1234 5678 9012 3456", "123", "12/25"));
        cart.checkout();
        
        // 別の注文でPayPal支払い
        ShoppingCart cart2 = new ShoppingCart();
        cart2.addItem(new Item("スマートフォン", 60000));
        cart2.setPaymentStrategy(new PayPalPayment("example@email.com", "password"));
        cart2.checkout();
    }
}

使用例

  • 支払い処理:クレジットカード、PayPal、銀行振込など
  • ソートアルゴリズム:クイックソート、マージソート、ヒープソートなど
  • 圧縮アルゴリズム:ZIP、RAR、7Zなど
  • ルート検索:最短距離、最短時間、最小コストなど
  • 認証方法:パスワード、OAuth、二要素認証など

メリット

  • アルゴリズムの分離:アルゴリズムをクライアントから分離し、独立して変更できる
  • 継承の代替:継承よりも柔軟なアルゴリズム切り替えが可能
  • 条件分岐の削減:複雑な条件分岐をポリモーフィズムに置き換えられる
  • 実行時の動的切り替え:アルゴリズムを実行時に変更できる

デメリット

  • クラス数の増加:アルゴリズムごとに新しいクラスが必要
  • コンテキストとストラテジーの通信:ストラテジー間で情報を共有する場合、設計が複雑になる可能性がある
  • クライアントの知識:クライアントが適切なストラテジーを選択するために、各ストラテジーの違いを理解する必要がある

関連パターン

  • Factory Method パターン:Strategy オブジェクトの生成に使用できる
  • Flyweight パターン:共有可能な Strategy オブジェクトの管理に使用できる
  • Template Method パターン:アルゴリズムの一部をサブクラスに委譲する別のアプローチ