Strategyパターン
CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 55/70。
ストラテジーパターン(Strategy pattern)は、一連のアルゴリズムを定義し、それぞれを独自のクラスにカプセル化して、実行時にそれらを入れ替え可能にする振る舞いに関するデザインパターンです。振る舞いを直接ハードコーディングする代わりに、目的のストラテジーをオブジェクトに注入します。
このパターンは、アルゴリズムの規約を定義するストラテジーインターフェース、異なる振る舞いを実装する具体的なストラテジークラス、そしてストラテジーを使用するコンテキストクラスの3つの部分で構成されています。
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via Credit Card");
}
public class PayPalPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via PayPal");
}
public class ShoppingCart
{
private IPaymentStrategy _paymentStrategy;
public void SetPaymentMethod(IPaymentStrategy strategy)
{
_paymentStrategy = strategy;
}
public void Checkout(decimal total)
{
_paymentStrategy.Pay(total);
}
}コンテキスト (ShoppingCart) は、どの支払い方法を使用しているかを知りません。提供された戦略が何であれ、単に Pay() を呼び出すだけです:
var cart = new ShoppingCart();
cart.SetPaymentMethod(new CreditCardPayment());
cart.Checkout(99.99m); // Paid 99.99 via Credit Card
cart.SetPaymentMethod(new PayPalPayment());
cart.Checkout(49.99m); // Paid 49.99 via PayPalStrategyパターンは、複雑な条件分岐ロジックを排除します。新しい支払い方法を追加するということは、IPaymentStrategyを実装する新しいクラスを作成することを意味します。ShoppingCartを変更する必要はありません。これにより、コードは拡張に対して開かれ、修正に対して閉じられた状態になります。
チャレンジ
簡単Strategy パターンを使用して、送料計算機を構築しましょう。異なる配送方法には異なる価格アルゴリズムがあり、システムはコアとなる注文処理ロジックを変更することなく、実行時にそれらを切り替えられるようにする必要があります。
コードは以下の3つのファイルに分けて構成します:
ShippingStrategy.cs:Shipping名前空間内に、doubleを返すCalculateCost(double weight)メソッドを持つIShippingStrategyインターフェースを定義します。次に、3つの具体的なストラテジークラスを作成します:StandardShipping- 送料をweight * 1.5として計算しますExpressShipping- 送料をweight * 3.0 + 5.0として計算します(一律の速達料金を含みます)OvernightShipping- 送料をweight * 5.0 + 10.0として計算します(基本料金が高いプレミアムサービスです)
OrderProcessor.cs: 同じ名前空間にOrderProcessorクラスを作成します。これは配送ストラテジーを使用するコンテキストクラスです。以下の要素を持つ必要があります:- 現在の
IShippingStrategyを保持するプライベートフィールド - 配送ストラテジーを変更するための
SetShippingMethod(IShippingStrategy strategy)メソッド - 現在のストラテジーを使用して送料を計算し、その結果を返す
ProcessOrder(double weight)メソッド
- 現在の
Program.cs:OrderProcessorを作成し、実行時に配送ストラテジーを入れ替える方法を実演して、すべてをまとめます。同じプロセッサが、ストラテジーを変更するだけで、異なるアルゴリズムを使用してコストを計算できます。
2つの入力を受け取ります:
- 配送方法のタイプ:
"standard"、"express"、または"overnight" - 小包の重量(小数、例:
4.5)
OrderProcessor を作成し、入力タイプに基づいて適切な配送ストラテジーを設定し、注文を処理して計算された送料を出力してください。
例えば、入力が "express" と 4.5 の場合、出力は以下のようになります:
18.5これは Strategy パターンの強力さを示しています。OrderProcessor 自体には配送計算ロジックが含まれていません。与えられたストラテジーに処理を委譲するだけなので、プロセッサを一切変更することなく、新しい配送方法(DroneDelivery など)を簡単に追加できます!
チートシート
Strategy パターンは、一連のアルゴリズムを定義し、それぞれを独自のクラスにカプセル化して、実行時にそれらを交換可能にする振る舞いに関するデザインパターンです。
このパターンは、次の3つの部分で構成されています。
- Strategy インターフェース: アルゴリズムの規約を定義します
- Concrete strategy(具体的な戦略)クラス: 異なる振る舞いを実装します
- Context(コンテキスト)クラス: 実装の詳細を知ることなく Strategy を使用します
構造の例:
// Strategy インターフェース
public interface IPaymentStrategy
{
void Pay(decimal amount);
}
// 具体的な戦略(Concrete strategies)
public class CreditCardPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via Credit Card");
}
public class PayPalPayment : IPaymentStrategy
{
public void Pay(decimal amount) =>
Console.WriteLine($"Paid {amount} via PayPal");
}
// Context クラス
public class ShoppingCart
{
private IPaymentStrategy _paymentStrategy;
public void SetPaymentMethod(IPaymentStrategy strategy)
{
_paymentStrategy = strategy;
}
public void Checkout(decimal total)
{
_paymentStrategy.Pay(total);
}
}使用例 - 実行時に戦略を切り替える:
var cart = new ShoppingCart();
cart.SetPaymentMethod(new CreditCardPayment());
cart.Checkout(99.99m); // クレジットカードで 99.99 を支払い済み
cart.SetPaymentMethod(new PayPalPayment());
cart.Checkout(49.99m); // PayPal で 49.99 を支払い済みメリット:
- 複雑な条件分岐ロジックを排除できる
- 拡張に対して開いており、修正に対して閉じている(開放閉鎖の原則)
- 新しい戦略を追加する際、新しいクラスを作成するだけで済み、既存のコードを変更する必要がない
自分で試してみよう
using System;
using Shipping;
class Program
{
public static void Main(string[] args)
{
// 入力を読み込む
string shippingType = Console.ReadLine();
double weight = Convert.ToDouble(Console.ReadLine());
// TODO: OrderProcessor インスタンスを作成する
// TODO: shippingType ("standard", "express", または "overnight") に基づいて、
// 適切な配送ストラテジーを作成し、プロセッサーに設定する
// TODO: 注文を処理し、計算されたコストを出力する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。