Menu
Coddy logo textTech

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 PayPal

Strategyパターンは、複雑な条件分岐ロジックを排除します。新しい支払い方法を追加するということは、IPaymentStrategyを実装する新しいクラスを作成することを意味します。ShoppingCartを変更する必要はありません。これにより、コードは拡張に対して開かれ、修正に対して閉じられた状態になります。

challenge icon

チャレンジ

簡単

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: 注文を処理し、計算されたコストを出力する
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン