Skip to content
intermediate Phase 10 · More Design Patterns

Strategy Pattern

Strategy pattern for defining families of algorithms and selecting them at runtime, with Magento shipping and payment examples

45m
0 problems
Topic Progress 0%

The Strategy Pattern Explained

Definition

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. It lets the algorithm vary independently from clients that use it.

Problem: Conditional Logic Explosion

namespace Vendor\Shipping\Model;

class ShippingCalculator
{
    public function calculate(string $method, float $weight, float $distance): float
    {
        return match ($method) {
            'flat_rate' => 5.99,
            'free' => 0.00,
            'weight_based' => $weight * 0.50,
            'distance_based' => $distance * 0.10,
            'express' => 15.99 + ($weight * 0.30),
            'overnight' => 29.99 + ($weight * 0.50) + ($distance * 0.15),
            default => throw new \InvalidArgumentException("Unknown method: {$method}")
        };
    }
}

Problems: Adding a new method modifies this class. Different methods have different parameters. Testing each algorithm requires testing the entire class.

Solution: Strategy Pattern

namespace Vendor\Shipping\Api;

interface ShippingStrategyInterface
{
    public function calculate(ShippingRequest $request): float;
    public function getCode(): string;
    public function getTitle(): string;
}

namespace Vendor\Shipping\Model\Strategy;

class FlatRateStrategy implements \Vendor\Shipping\Api\ShippingStrategyInterface
{
    private float $rate = 5.99;

    public function calculate(\Vendor\Shipping\Api\ShippingRequest $request): float
    {
        return $this->rate;
    }

    public function getCode(): string { return 'flat_rate'; }
    public function getTitle(): string { return 'Flat Rate'; }
}

class WeightBasedStrategy implements \Vendor\Shipping\Api\ShippingStrategyInterface
{
    private float $pricePerKg = 0.50;

    public function calculate(\Vendor\Shipping\Api\ShippingRequest $request): float
    {
        return $request->getWeight() * $this->pricePerKg;
    }

    public function getCode(): string { return 'weight_based'; }
    public function getTitle(): string { return 'Weight Based'; }
}

class FreeShippingStrategy implements \Vendor\Shipping\Api\ShippingStrategyInterface
{
    public function calculate(\Vendor\Shipping\Api\ShippingRequest $request): float
    {
        return 0.00;
    }

    public function getCode(): string { return 'free'; }
    public function getTitle(): string { return 'Free Shipping'; }
}

// Context: uses strategies interchangeably
class ShippingCalculator
{
    private array $strategies = [];

    public function __construct(iterable $strategies)
    {
        foreach ($strategies as $strategy) {
            $this->strategies[$strategy->getCode()] = $strategy;
        }
    }

    public function calculate(string $method, ShippingRequest $request): float
    {
        if (!isset($this->strategies[$method])) {
            throw new \InvalidArgumentException("Unknown shipping method: {$method}");
        }
        return $this->strategies[$method]->calculate($request);
    }
}

Adding a new shipping method = creating a new class. Zero modifications to existing code.

Strategy in Magento: Shipping Methods

Magento's Shipping Carrier Architecture

Magento uses the Strategy pattern for shipping carriers. Each carrier implements CarrierInterface:

// Magento\Shipping\Model\Carrier\CarrierInterface
interface CarrierInterface
{
    public function collectRates(
        \Magento\Shipping\Request $request
    ): \Magento\Quote\Model\Quote\Address\RateResultInterface;

    public function getCode(string $code);
}

// Each carrier is a strategy
class Fedex implements CarrierInterface
{
    public function collectRates(\Magento\Shipping\Request $request)
    {
        // FedEx API call to get real-time rates
        $rates = $this->fedexApi->getRates([
            'origin' => $request->getOriginZipcode(),
            'destination' => $request->getDestZipcode(),
            'weight' => $request->getPackageWeight(),
        ]);
        return $this->buildResult($rates);
    }
}

class FreeShipping implements CarrierInterface
{
    public function collectRates(\Magento\Shipping\Request $request)
    {
        $result = $this->rateFactory->create();
        $result->append($this->createFreeMethod());
        return $result;
    }
}

// The carrier code is selected at runtime based on configuration
class ShippingConfig
{
    public function getActiveCarriers(): array
    {
        return $this->config->getValue('carriers');
    }
}

The active shipping method is determined by configuration, and the corresponding strategy (carrier) handles rate calculation.

Strategy in Magento: Payment Processing

Payment Method Strategies

// Each payment method is a strategy
interface PaymentMethodInterface
{
    public function authorize(
        \Magento\Payment\Model\InfoInterface $payment,
        float $amount
    ): bool;

    public function capture(
        \Magento\Payment\Model\InfoInterface $payment,
        float $amount
    ): bool;

    public function refund(
        \Magento\Payment\Model\InfoInterface $payment,
        float $amount
    ): bool;
}

class CreditCardMethod implements PaymentMethodInterface
{
    public function authorize(\Magento\Payment\Model\InfoInterface $payment, float $amount): bool
    {
        return $this->gateway->authorize($payment->getData('cc_number'), $amount);
    }

    public function capture(\Magento\Payment\Model\InfoInterface $payment, float $amount): bool
    {
        return $this->gateway->capture($payment->getTransactionId(), $amount);
    }
}

class BankTransferMethod implements PaymentMethodInterface
{
    public function authorize(\Magento\Payment\Model\InfoInterface $payment, float $amount): bool
    {
        // No real-time authorization — pending until bank confirms
        return true;
    }

    public function capture(\Magento\Payment\Model\InfoInterface $payment, float $amount): bool
    {
        // Capture happens when bank transfer is confirmed
        return $this->bankApi->checkTransferStatus($payment->getTransactionId());
    }
}

// Context: Payment processor uses the selected strategy
class PaymentProcessor
{
    public function process(
        PaymentMethodInterface $method,
        \Magento\Payment\Model\InfoInterface $payment,
        float $amount
    ): bool {
        if (!$method->authorize($payment, $amount)) {
            return false;
        }
        return $method->capture($payment, $amount);
    }
}

The payment method is selected at checkout based on store configuration. The PaymentProcessor doesn't care which strategy handles the payment.

Quiz

1. The Strategy pattern is best used when:

Question 1 options

2. How does the Strategy pattern differ from a switch statement?

Question 2 options

3. In Magento, shipping carriers are an example of:

Question 3 options

Flashcards

Question

What does the Strategy pattern do?

Answer

Defines a family of algorithms, encapsulates each, and makes them interchangeable

Question

Strategy vs switch statement?

Answer

Strategy: each algorithm in its own class (OCP). Switch: all logic in one place.

Question

Magento shipping carriers use which pattern?

Answer

Strategy pattern — each carrier implements CarrierInterface differently

Revision Notes

Key Takeaways

  • 1. Strategy defines a family of algorithms behind a common interface
  • 2. Each strategy is a separate class, making adding new algorithms easy
  • 3. Context selects and uses strategies at runtime
  • 4. Magento uses Strategy for shipping carriers, payment methods, tax calculators
  • 5. Strategy follows OCP: new algorithms = new classes, no modifications

Interview Tips

  • Give Magento examples: shipping carriers, payment methods, tax calculators
  • Explain why Strategy beats switch: OCP compliance, testability, separation
  • Discuss when Strategy is overkill (2-3 simple algorithms)

Cheat Sheet

Strategy Pattern:
  Interface → ShippingStrategyInterface
  Strategies → FlatRate, WeightBased, FreeShipping, Express
  Context   → ShippingCalculator (selects and uses strategy)

Magento Examples:
  Shipping: CarrierInterface (FedEx, UPS, DHL)
  Payment:  PaymentMethodInterface (CreditCard, PayPal)
  Tax:      TaxCalculatorInterface (VAT, GST, SalesTax)

When to use: multiple algorithms, runtime selection, need to add new ones