Overview

The Strategy Pattern is a behavioural design pattern that enables selecting an algorithm’s behaviour at runtime. Instead of embedding multiple conditional statements to determine which algorithm to execute, the Strategy Pattern allows you to define a family of algorithms, encapsulate them in separate classes, and make them interchangeable.

Key Benefits:

  • Eliminates large conditional statements (e.g., if-else or switch blocks).

  • Makes the code more maintainable and scalable.

  • Allows for runtime behaviour changes without modifying client code.

This pattern promotes code reusability, maintainability, and flexibility, as new strategies can be added without modifying existing code. It adheres to the Open/Closed Principle by allowing new behaviours without altering the core logic.

Imagine an e-commerce system that supports multiple payment methods (Credit Card, PayPal, and Bank Transfer). Without the Strategy Pattern, you might have a large if-else block to handle different payment methods. This approach quickly becomes messy and hard to maintain as new payment options are introduced.

Using the Strategy Pattern, each payment method is encapsulated in its own class, and the appropriate strategy is selected dynamically.

Example

Let’s write up a quick example to calculate the area of a shape. First, as usual, I define an interface.

interface Shape
{
    public function getArea(): float;
}

With that in place, we can create some concrete classes which implement this interface.

class Circle implements Shape
{
    public function __construct(private readonly float $radius)
    {

    }

    public function getArea(): float
    {
        return M_PI * $this->radius ** 2;
    }
}

class Rectangle implements Shape
{
    public function __construct(
        private readonly float $width, 
        private readonly float $height
    )
    {
     
    }

    public function getArea(): float
    {
        return $this->width * $this->height;
    }
}

class Triangle implements Shape
{
    public function __construct(
        private readonly float $base, 
        private readonly float $height
    )
    {

    }

    public function getArea(): float
    {
        return 0.5 * $this->base * $this->height;
    }
}

And that’s essentially it. Let’s put it together.

$shapes = [
    new Circle(3),
    new Rectangle(4, 5),
    new Triangle(6, 4),
];

foreach ($shapes as $shape) {
    echo $shape::class . " area: " . $shape->getArea() . PHP_EOL;
}

Because each class is forced to implement the same interface, we KNOW each class has a getArea method.

Here’s a few advantages:

  • No If-Else Hell

    • You avoid a long chain of if-else or switch statements that grow messy as more shapes are added.

  • Open/Closed Principle (OCP)

    • You can add new shapes (e.g., Hexagon, Pentagon) without modifying existing code—just create a new class that implements getArea().

  • Encapsulation

    • Each shape handles its own area calculation inside its class, keeping logic self-contained and preventing duplicated code.

  • Code Readability & Maintainability

    • Your code is cleaner, easier to read, and more maintainable compared to an if-else structure that mixes data handling with logic.

  • Better Scalability

    • New shapes can be added with minimal changes, making the code more future-proof.

  • Single Responsibility Principle (SRP)

    • Each class focuses on one task: calculating the area of a shape. This keeps concerns separate and easier to manage.

But how would this look without using the Strategy pattern?

$shapes = [
    ['type' => 'circle', 'radius' => 3],
    ['type' => 'rectangle', 'width' => 4, 'height' => 5],
    ['type' => 'triangle', 'base' => 6, 'height' => 4],
];

foreach ($shapes as $shape) {
    if ($shape['type'] === 'circle') {
        $area = pi() * pow($shape['radius'], 2);
        echo "Circle area: $area" . PHP_EOL;
    } elseif ($shape['type'] === 'rectangle') {
        $area = $shape['width'] * $shape['height'];
        echo "Rectangle area: $area" . PHP_EOL;
    } elseif ($shape['type'] === 'triangle') {
        $area = 0.5 * $shape['base'] * $shape['height'];
        echo "Triangle area: $area" . PHP_EOL;
    } else {
        echo "Unknown shape type." . PHP_EOL;
    }
}

Why is This Problematic?

  • Rigid structure – Adding a new shape requires modifying the if-else block.

  • Less maintainable – The logic is cluttered and harder to extend.

  • Type-dependent processing – Instead of letting objects handle their own behaviour, we manually check and calculate areas.

This works, but it's not scalable. The better approach (as in your original example) is to use polymorphism with classes, where each shape handles its own getArea() calculation.

Want to see the Strategy Pattern in action? Check out this simple PHP example: Github