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";