Strategy
The Strategy pattern defines a family of interchangeable algorithms, wraps each one in its own class behind a shared interface, and lets client code swap between them at runtime. Instead of one method that "knows" every possible algorithm and picks between them with conditionals, you get many small classes that each know exactly one algorithm — and a client that simply holds a reference to whichever one it currently needs.
The Problem: An If/Elseif Chain of Algorithms
A very common way this problem shows up is a method that picks between several ways of calculating something, based on a string or constant passed in. Here's a shipping cost calculator that starts innocently enough and grows a branch every time a new shipping method is added:
<?php
class ShippingCalculator {
public function calculateShipping(string $method, float $weight): float {
if ($method === 'standard') {
return $weight * 0.5;
} elseif ($method === 'express') {
return $weight * 0.5 + 10;
} elseif ($method === 'overnight') {
return $weight * 0.5 + 25;
}
throw new InvalidArgumentException("Unknown shipping method: $method");
}
}
This works, but every new shipping option means opening ShippingCalculator and editing
a method that's already tested and working — risking a typo that breaks standard shipping
while you're only trying to add economy shipping. The algorithms are also impossible to
reuse or test in isolation; they're all welded together inside one method.
What Strategy Actually Does
Strategy pulls each branch out into its own class, all implementing one shared interface:
- A common interface declares the method every algorithm must provide (here,
calculate()). - Each concrete algorithm becomes its own small class implementing that interface.
- The client (the code that needs the result) holds a reference to a strategy object and simply calls the interface method — it never checks "which algorithm is this?"
A Realistic Worked Example: Shipping Cost Calculation
First, a shared interface every shipping strategy must implement:
<?php
interface ShippingStrategyInterface {
public function calculate(float $weight): float;
}
Then one small class per algorithm — each is trivial to read, test, and change on its own:
<?php
class StandardShipping implements ShippingStrategyInterface {
public function calculate(float $weight): float {
return $weight * 0.5;
}
}
class ExpressShipping implements ShippingStrategyInterface {
public function calculate(float $weight): float {
return $weight * 0.5 + 10;
}
}
class OvernightShipping implements ShippingStrategyInterface {
public function calculate(float $weight): float {
return $weight * 0.5 + 25;
}
}
And a ShoppingCart that accepts a strategy object and delegates to it, with no idea what
algorithm actually runs behind it:
<?php
class ShoppingCart {
private ShippingStrategyInterface $shippingStrategy;
public function __construct(ShippingStrategyInterface $shippingStrategy) {
$this->shippingStrategy = $shippingStrategy;
}
// Swap the algorithm at runtime — no other code in ShoppingCart needs to change
public function setShippingStrategy(ShippingStrategyInterface $shippingStrategy): void {
$this->shippingStrategy = $shippingStrategy;
}
public function getShippingCost(float $weight): float {
return $this->shippingStrategy->calculate($weight);
}
}
// Usage — the customer picked "express" at checkout
$cart = new ShoppingCart(new ExpressShipping());
$cost = $cart->getShippingCost(3.2); // 11.6
EconomyShipping class implementing
ShippingStrategyInterface, and pass it in wherever it's needed. ShoppingCart,
StandardShipping, ExpressShipping, and OvernightShipping are
never touched.
Why This Is Better
This is the Open/Closed Principle in action: classes should be open for extension but closed for modification. With the if/elseif version, "extension" meant editing existing, working code. With Strategy, extension means adding a brand-new class — every existing class stays untouched, untested code paths can't be accidentally broken, and each algorithm can be unit tested completely on its own by instantiating just that one strategy class.
Strategy vs Factory Method
It's easy to mix these two up because both involve an interface and multiple implementing classes. The difference is what's being swapped:
| Pattern | What varies |
|---|---|
| Strategy | Which algorithm/behavior runs for a task the caller is already going to perform (how to calculate shipping) |
| Factory Method | Which concrete object gets created in the first place (which class to instantiate) |
In practice they often work together: a Factory Method might be exactly what decides which
ShippingStrategyInterface implementation to construct based on the customer's choice,
keeping that decision out of ShoppingCart entirely.