ストラテジーパターン

 条件分岐が多く可読性や保守性が悪いコードから、ストラテジーパターンを適用して可読性を向上させるサンプルコードを示します。
このサンプルコードは、商品の割引計算に関連しています。

ストラテジーパターン適用前

 このコードでは、割引タイプごとに条件分岐が行われ、可読性が低く保守性が悪い状態です。
割引は一般的に運用を続けるうちに施策として増え続ける傾向にあります。
割引タイプが増えれば増えるほど条件分岐が増えてソースコードの状態が悪くなっていきます。

<?php

enum DiscountType
{
    case CHRISTMAS;
    case NEW_YEAR;
    case EASTER;
    case BIRTHDAY;
}
                        
<?php

class Product
{
    private int $price;

    public function __construct(int $price)
    {
        $this->price = $price;
    }

    public function getPrice(): int
    {
        return $this->price;
    }
}
                        
<?php

class DiscountCalculator
{
    public function calculateDiscount(Product $product, DiscountType $discountType): float
    {
        if ($discountType === DiscountType::CHRISTMAS) {
            return $product->getPrice() * 0.2;
        } elseif ($discountType === DiscountType::NEW_YEAR) {
            return $product->getPrice() * 0.15;
        } elseif ($discountType === DiscountType::EASTER) {
            return $product->getPrice() * 0.1;
        } elseif ($discountType === DiscountType::BIRTHDAY) {
            return $product->getPrice() * 0.25;
        }

        return 0.0;
    }
}
                        
<?php

// 価格が100ドルの商品
$product = new Product(100);

// クリスマス割引を適用
$calculator = new DiscountCalculator();
$discount = $calculator->calculateDiscount($product, DiscountType::CHRISTMAS);
echo "クリスマス割引: $discount\n";

// 誕生日割引を適用
$calculator = new DiscountCalculator();
$discount = $calculator->calculateDiscount($product, DiscountType::BIRTHDAY);
echo "誕生日割引: $discount\n";
                        

ストラテジーパターン適用後

 このコードでは、DiscountStrategyインターフェースを使用して割引計算メソッドを定義し、それぞれの要件を個別のクラスにカプセル化しました。
これにより、条件分岐が排除され、ストラテジーパターンが適用されました。
割引の要件が追加される場合は既存のソースコードには触れずに新しいクラスを実装するだけで済みます。
可読性が向上し、保守性が良くなりました。

<?php

class Product
{
    private int $price;

    public function __construct(int $price)
    {
        $this->price = $price;
    }

    public function getPrice(): int
    {
        return $this->price;
    }
}
                        
<?php

interface DiscountStrategy
{
    public function calculateDiscount(Product $product): float;
}
                        
<?php

class ChristmasDiscount implements DiscountStrategy
{
    public function calculateDiscount(Product $product): float
    {
        return $product->getPrice() * 0.2;
    }
}
                        
<?php

class NewYearDiscount implements DiscountStrategy
{
    public function calculateDiscount(Product $product): float
    {
        return $product->getPrice() * 0.15;
    }
}
                        
<?php

class EasterDiscount implements DiscountStrategy
{
    public function calculateDiscount(Product $product): float
    {
        return $product->getPrice() * 0.1;
    }
}
                        
<?php

class BirthdayDiscount implements DiscountStrategy
{
    public function calculateDiscount(Product $product): float
    {
        return $product->getPrice() * 0.25;
    }
}
                        
<?php

class DiscountCalculator
{
    private DiscountStrategy $discountStrategy;

    public function __construct(DiscountStrategy $discountStrategy)
    {
        $this->discountStrategy = $discountStrategy;
    }

    public function calculateDiscount(Product $product): float
    {
        // if文の制御が無くなり、割引タイプが増えても改修をする必要がなくなった
        return $this->discountStrategy->calculateDiscount($product);
    }
}
                        
<?php

// 価格が100ドルの商品
$product = new Product(100);

// クリスマス割引を適用
$calculator = new DiscountCalculator(new ChristmasDiscount());
$discount = $calculator->calculateDiscount($product);
echo "クリスマス割引: $discount\n";

// 誕生日割引を適用
$calculator = new DiscountCalculator(new BirthdayDiscount());
$discount = $calculator->calculateDiscount($product);
echo "誕生日割引: $discount\n";