Objects should be open for extension but closed for modification.


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

if ($itemType == 'bread') {
$discount = $this->getDiscount(0.2, $itemPrice, $unitTotal); # 20%
} elseif ($itemType == 'rice') {
$discount = $this->getDiscount(0.4, $itemPrice, $unitTotal); # 40%
}

return $discount;
}

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

$discountCalculator = new DiscountCalculator();
echo $discountCalculator->calculate('bread', 1.00, 5);
echo $discountCalculator->calculate('rice', 3.00, 4);

As you can see above, our DiscountCalculator class is not a good candidate for extension. It looks more like a class which should be modified if we wanted to add more item types to it. That's why this is a violation.


# REFACTORED
interface ItemInterface
{
public function setDiscountValue(float $discountValue);
public function getDiscountValue(): float;
}

class Bread implements ItemInterface
{
private $discountValue;

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

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

class Rice implements ItemInterface
{
private $discountValue;

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

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

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

$discountCalculator = new DiscountCalculator();

$bread = new Bread();
$bread->setDiscountValue(0.2);

$rice = new Rice();
$rice->setDiscountValue(0.4);

echo $discountCalculator->calculate($bread, 1.00, 5);
echo $discountCalculator->calculate($rice, 3.00, 4);

As you can see above, we now have a Bread and a Rice classes. Also the DiscountCalculator is a pure calculator class. In future, If we want to calculate discounts for a new item, all we have to do is, create a new dedicated class just like Bread or Rice then inject it into DiscountCalculator class. As a result, no modification would be needed anywhere.