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 パターン:アルゴリズムの一部をサブクラスに委譲する別のアプローチ