Refactoring the Switch with Strategy design pattern in PHP

Strategy pattern can be very useful to replace switches. Of course this justifies only when switch cases have a lot of code that and is also susceptible to change. This justifies spreading the code into multiple specialized classes that will have multiple methods each. Below is a switch that can be replaced with the strategy. We will assume that the switch cases contain a lot of business code that is susceptible to change.

class SwitchTaxCalculator
{
    public function calculate(float $price, string $region): float
    {
        switch ($region) {
            case "US":
                //here we assume there is a lot of code
                $tax = $price * 0.2;
                break;
            case "UK":
                //here we assume there is a lot of code
                $tax = $price * 0.3;
                break;
            case "EMEA":
                //here we assume there is a lot of code
                $tax = $price * 0.4;
                break;
            default:
                throw new Exception('Region not supported');
        }

        return $tax;
    }
}

//test
echo (new SwitchTaxCalculator())->calculate(5, 'US') . "\n";
echo (new SwitchTaxCalculator())->calculate(5, 'UK') . "\n";
echo (new SwitchTaxCalculator())->calculate(5, 'EMEA') . "\n";


// the refactor into strategy pattern
interface Tax
{
    public function getRegion(): string;

    public function calculate(float $price): float;
}

class USTax implements Tax
{
    public function getRegion(): string
    {
        return 'US';
    }

    public function calculate(float $price): float
    {
        return $price * 0.2;
    }
}

class UKTax implements Tax
{
    public function getRegion(): string
    {
        return 'UK';
    }

    public function calculate(float $price): float
    {
        return $price * 0.3;
    }
}

class EMEATax implements Tax
{
    public function getRegion(): string
    {
        return 'EMEA';
    }

    public function calculate(float $price): float
    {
        return $price * 0.4;
    }
}

class StrategyTaxCalculator
{
    public function calculate(float $price, string $region)
    {
        /** @var Tax[] $taxCalculators */
        $taxCalculators = [new USTax(), new UKTax(), new EMEATax()];

        foreach ($taxCalculators as $taxCalculator) {
            if ($taxCalculator->getRegion() === $region) {
                return $taxCalculator->calculate($price);
            }
        }

        throw new Exception('Region not supported');
    }
}

// test
echo (new StrategyTaxCalculator())->calculate(5, 'US') . "\n";
echo (new StrategyTaxCalculator())->calculate(5, 'UK') . "\n";
echo (new StrategyTaxCalculator())->calculate(5, 'EMEA') . "\n";

If on the other hand the switch contains fewer lines of code we can extract the code of each of the case into its own method and leave behind a hygienic switch.

Switch hygiene rules:

  • is the only instruction in the method.
  • default: throw exception.
  • one line per case.
class HygienicSwitchTaxCalculator
{
    public function calculate(float $price, string $region): float
    {
        switch ($region) {
            case "US":
                return $this->calculateUSPrice($price)
            case "UK":
                return $this->calculateUKPrice($price);
            case "EMEA":
                return $this->calculateEMEAPrice($price);
            default:
                throw new Exception('Region not supported');
        }
    }

    public function calculateUSPrice(float $price): float
    {
        //here we assume there is fewer code than needed to use the strategy pattern but more than one line of code
        return $price * 0.2;
    }

    public function calculateUKPrice(float $price): float
    {
        //here we assume there is fewer code than needed to use the strategy pattern but more than one line of code
        return $price * 0.3;
    }

    public function calculateEMEAPrice(float $price): float
    {
        //here we assume there is fewer code than needed to use the strategy pattern but more than one line of code
        return $price * 0.4;
    }
}

//test
echo (new HygienicSwitchTaxCalculator())->calculate(5, 'US') . "\n";
echo (new HygienicSwitchTaxCalculator())->calculate(5, 'UK') . "\n";
echo (new HygienicSwitchTaxCalculator())->calculate(5, 'EMEA') . "\n";

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.