Menu
Coddy logo textTech

Strategyパターン

CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 74/91。

Strategy Patternは、アルゴリズムのファミリーを定義し、それぞれを独自のクラスにカプセル化して、実行時に交換可能にする振る舞いに関するデザインパターンです。ストラテジーを使用するオブジェクトは、どのアルゴリズムを実行しているのかを知る必要がありません。

さまざまな discount 戦略を適用できるショッピングカートを想像してください。条件分岐で discount ロジックをハードコーディングする代わりに、それぞれの discount を個別の class として定義します。

<?php
interface DiscountStrategy {
    public function calculate(float $total): float;
}

class NoDiscount implements DiscountStrategy {
    public function calculate(float $total): float {
        return $total;
    }
}

class PercentageDiscount implements DiscountStrategy {
    public function __construct(private int $percent) {}
    
    public function calculate(float $total): float {
        return $total - ($total * $this->percent / 100);
    }
}

class FixedDiscount implements DiscountStrategy {
    public function __construct(private float $amount) {}
    
    public function calculate(float $total): float {
        return max(0, $total - $this->amount);
    }
}

context class は任意の戦略を受け入れ、計算を delegates します:

<?php
class ShoppingCart {
    public function __construct(private DiscountStrategy $discount) {}
    
    public function setDiscount(DiscountStrategy $discount): void {
        $this->discount = $discount;
    }
    
    public function checkout(float $total): float {
        return $this->discount->calculate($total);
    }
}

$cart = new ShoppingCart(new NoDiscount());
echo $cart->checkout(100) . "\n";

$cart->setDiscount(new PercentageDiscount(20));
echo $cart->checkout(100);

出力:

100
80

Strategy Patternは、操作を実行する複数の方法があり、それらを使用するclassを変更せずに切り替えたい場合に力を発揮します。新しいdiscountの種類を追加するには、新しいclassを作成するだけでよく、既存のコードに手を加える必要はありません。

challenge icon

チャレンジ

簡単

Strategy Patternを使用して送料計算機を作成しましょう。配送方法ごとに異なる料金ルールがあります。通常配送は固定料金を請求し、速達配送は割合を加算し、翌日配送はその両方を組み合わせます。Strategy Patternを使うと、それらを使用するコードを変更せずに、これらの計算を入れ替えられます。

コードを4つのファイルに分けて構成します。

  • ShippingStrategy.php:すべての配送方法の contract をdefineする ShippingStrategy interface をCreateします。荷物の重量を受け取り、送料を返す calculate(float $weight): float メソッドを持たせます。
  • ShippingMethods.php:ShippingStrategy ファイルを読み込み、interfaceをimplementsする3つのクラスをCreateします。
    • StandardShipping:重量に関係なく、$5.00 のflat rateを請求します
    • ExpressShipping:1 kilogramあたり $2.00 を請求します(重量に2を掛けます)
    • OvernightShipping:base feeとして $10.00 に加え、1 kilogramあたり $3.00 を請求します
  • ShippingCalculator.php:ShippingStrategy ファイルを読み込み、contextとしてactsする ShippingCalculator クラスをCreateします。calculatorには次の機能を持たせます。
    • constructorで ShippingStrategy をAcceptします(constructor promotionを使用します)
    • 実行時に配送方法をchangeするための setStrategy(ShippingStrategy $strategy): void メソッドを持たせます
    • current strategyにdelegatesする calculateCost(float $weight): float メソッドを持たせます
  • main.php:ShippingMethods ファイルと ShippingCalculator ファイルを読み込みます。2つのinputを受け取ります。配送タイプ("standard""express"、または "overnight")と荷物の重量です。

    StandardShippingで開始する ShippingCalculator をCreateします。配送タイプのinputに基づき、setStrategy() を使用して appropriate な配送方法に切り替えます。その後、given の重量のcostを計算し、小数点以下2桁にformattedして結果を出力します。

    出力は次のようになります:Shipping cost: $[cost]

このchallengeでは、Strategy Patternによってshipping systemがどのようにflexibleになるかを示します。SameDayShipping のような新しい配送方法を追加する場合、calculatorや既存の配送方法に触れることなく、interfaceをimplementsする新しいクラスをCreateするだけで済みます。

自分で試してみよう

<?php
require_once 'ShippingMethods.php';
require_once 'ShippingCalculator.php';

// 入力を読み取る
$shippingType = trim(fgets(STDIN));
$weight = floatval(fgets(STDIN));

// TODO: StandardShippingで始まるShippingCalculatorを作成する

// TODO: 配送タイプの入力("standard"、"express"、または"overnight")に基づいて、
// setStrategy()を使用して適切な配送方法に切り替える

// TODO: 指定された重量のコストを計算する

// TODO: 結果を小数点以下2桁にフォーマットして出力する
// Output format: Shipping cost: $[cost]

?>
quiz icon腕試し

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

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

自分で練習してみよう: PHPオンラインコンパイラ