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
Adding "Economy" shipping later: create an 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:

PatternWhat varies
StrategyWhich algorithm/behavior runs for a task the caller is already going to perform (how to calculate shipping)
Factory MethodWhich 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.

Up next: Observer tackles a different kind of communication problem — notifying multiple, decoupled parts of a system automatically when something changes.