んだ日記

ndaDayoの技術日記です

開放/閉鎖原則(open/closed principle、OCP)について

んだです。メモです。

開放/閉鎖原則(open/closed principle、OCP) とは?

定義

「モジュールは拡張に対して開いて (Open) おり,修正に対して閉じて (Closed) いなければならない」

拡張に対して開いているって? (Open)

仕様変更があった場合に、新しいコードを追加するだけで目的を達成できる

修正に対して閉じているって?? (Closed)

仕様変更があった場合に、クラスを修正する必要がないこと

この2つを同時に満たしている必要があります。

OCP違反なコード

ランジェリーショップの割引価格を扱うクラスがあったとします。

パンティーなら、0.5
ブラジャーなら、0.2

の割引率。

class DiscountCalculator
{
    public function calculate(object $itemType, int $itemPrice, int $unitTotal): float
    {
        $discount = 0;

        if ($itemType instanceof UnderWear) {
            $discount = $this->getDiscountPrice(0.5, $itemPrice, $unitTotal);
        } elseif ($itemType instanceof Bra) {
            $discount = $this->getDiscountPrice(0.2, $itemPrice, $unitTotal);
        }

        return $discount;
    }

    private function getDiscountPrice(float $discount, int $itemPrice, int $unitTotal)
    {
        return ($itemPrice * $unitTotal) * $discount;
    }
}

このランジェリーショップに、こんな仕様変更があったとする

パンストも商品として取り扱いたい、もちろん割引(0.4)もありで!!

この仕様変更に対しては、DiscountCalculatorクラスを修正する必要があります。。

これがOCP違反。

 public function calculate(object $itemType, int $itemPrice, int $unitTotal): float
    {
        $discount = 0;

        if ($itemType instanceof UnderWear) {
            $discount = $this->getDiscountPrice(0.5, $itemPrice, $unitTotal);
        } elseif ($itemType instanceof Bra) {
            $discount = $this->getDiscountPrice(0.2, $itemPrice, $unitTotal);
         // パンストの条件を追加
        } elseif ($itemType instanceof Pansuto) {
            $discount = $this->getDiscountPrice(0.4, $itemPrice, $unitTotal);
        }

        return $discount;
    }

リファクタリング

//  ItemInterface.php
interface ItemInterface
{
    public function setDiscountValue($discountValue);

    public function getDiscountValue();
}

// UnderWear.php
class UnderWear implements ItemInterface
{
    private $discountValue;

    public function setDiscountValue($discountValue)
    {
        $this->discountValue = $discountValue;
    }

    public function getDiscountValue()
    {
        return $this->discountValue;
    }
}


// DiscountCalculator.php

class DiscountCalculator
{
    /**
     * @param ItemInterface $itemType
     * @param int           $itemPrice
     * @param int           $unitTotal
     * @return float
     */
    public function calculate(ItemInterface $itemType, int $itemPrice, int $unitTotal): float
    {
        return ($itemPrice * $unitTotal) * $itemType->getDiscountValue();
    }
}