← Tilbage til patterns

Strategy Pattern

Definerer en familie af algoritmer, indkapsler hver enkelt, og gør dem udskiftelige. Strategy lader algoritmen variere uafhængigt af klienter der bruger den.

BehavioralBegynder-venlig

Om Patterned

Strategy Pattern er et behavioral design pattern introduceret af Gang of Four i 1994, der løser problemet med at vælge mellem forskellige algoritmer eller behaviors runtime uden at bruge conditionals. Patterned fungerer ved at definere en familie af algoritmer som separate klasser der implementerer samme interface, hvilket gør dem udskiftelige og testbare. Dette er fundamentalt i situationer hvor du har multiple måder at udføre samme opgave på, såsom forskellige sorteringsalgoritmer, betalingsmetoder, eller valideringsregler. Strategy Pattern eliminerer behovet for lange if-else eller switch statements og fremmer Open/Closed Principle ved at gøre det let at tilføje nye strategier uden at ændre eksisterende kode. I moderne PHP udvikling bruges Strategy Pattern hyppigt i frameworks til at håndtere forskellige authentication methods, cache drivers, og serialization formats.

Key Points

  • Indkapsler algoritmer i separate klasser
  • Alle strategier implementerer samme interface
  • Algoritmer kan skiftes runtime
  • Eliminerer lange conditional statements
  • Context klasse delegerer arbejde til strategy objekter
  • Følger Open/Closed Principle
  • Fremmer Single Responsibility Principle
  • Gør algoritmer lettere at teste isoleret
  • Client kan vælge passende strategi
  • Strategier kan deles mellem forskellige contexts
  • Kan kombineres med Dependency Injection
  • Alternative til arv for at opnå forskellige behaviors

Kode Eksempel

<?php

declare(strict_types=1);

namespace App\Shipping;

/**
 * Strategy Interface
 */
interface ShippingStrategyInterface
{
    public function calculate(float $weight, string $destination): float;
    public function getEstimatedDays(): int;
    public function getName(): string;
}

/**
 * Concrete Strategy - Standard Shipping
 */
final readonly class StandardShippingStrategy implements ShippingStrategyInterface
{
    public function calculate(float $weight, string $destination): float
    {
        $baseRate = 50.00;
        $perKgRate = 10.00;
        
        return $baseRate + ($weight * $perKgRate);
    }
    
    public function getEstimatedDays(): int
    {
        return 5;
    }
    
    public function getName(): string
    {
        return 'Standard Shipping';
    }
}

/**
 * Concrete Strategy - Express Shipping
 */
final readonly class ExpressShippingStrategy implements ShippingStrategyInterface
{
    public function calculate(float $weight, string $destination): float
    {
        $baseRate = 150.00;
        $perKgRate = 25.00;
        
        return $baseRate + ($weight * $perKgRate);
    }
    
    public function getEstimatedDays(): int
    {
        return 2;
    }
    
    public function getName(): string
    {
        return 'Express Shipping';
    }
}

/**
 * Concrete Strategy - International Shipping
 */
final readonly class InternationalShippingStrategy implements ShippingStrategyInterface
{
    public function calculate(float $weight, string $destination): float
    {
        $baseRate = 200.00;
        $perKgRate = 35.00;
        
        // Tillæg baseret på destination
        $destinationSurcharge = match(true) {
            str_starts_with($destination, 'US') => 100.00,
            str_starts_with($destination, 'EU') => 50.00,
            default => 150.00
        };
        
        return $baseRate + ($weight * $perKgRate) + $destinationSurcharge;
    }
    
    public function getEstimatedDays(): int
    {
        return 10;
    }
    
    public function getName(): string
    {
        return 'International Shipping';
    }
}

/**
 * Concrete Strategy - Pickup
 */
final readonly class PickupStrategy implements ShippingStrategyInterface
{
    public function calculate(float $weight, string $destination): float
    {
        return 0.00; // Gratis ved afhentning
    }
    
    public function getEstimatedDays(): int
    {
        return 1;
    }
    
    public function getName(): string
    {
        return 'Store Pickup';
    }
}

/**
 * Context - ShippingCalculator
 */
final class ShippingCalculator
{
    private ShippingStrategyInterface $strategy;
    
    public function __construct(ShippingStrategyInterface $strategy)
    {
        $this->strategy = $strategy;
    }
    
    public function setStrategy(ShippingStrategyInterface $strategy): void
    {
        $this->strategy = $strategy;
    }
    
    public function calculateShipping(float $weight, string $destination): array
    {
        $cost = $this->strategy->calculate($weight, $destination);
        $days = $this->strategy->getEstimatedDays();
        $name = $this->strategy->getName();
        
        return [
            'method' => $name,
            'cost' => $cost,
            'estimated_days' => $days,
        ];
    }
    
    public function compareAllStrategies(
        float $weight,
        string $destination,
        array $strategies
    ): array {
        $results = [];
        
        foreach ($strategies as $strategy) {
            $this->setStrategy($strategy);
            $results[] = $this->calculateShipping($weight, $destination);
        }
        
        return $results;
    }
}

// Brug af Strategy Pattern
$weight = 5.0; // kg
$destination = 'Copenhagen, Denmark';

// Alle tilgængelige strategier
$strategies = [
    new StandardShippingStrategy(),
    new ExpressShippingStrategy(),
    new InternationalShippingStrategy(),
    new PickupStrategy(),
];

// Opret calculator med initial strategi
$calculator = new ShippingCalculator(new StandardShippingStrategy());

echo "Shipping options for {$weight}kg to {$destination}:\n\n";

// Sammenlign alle strategier
$options = $calculator->compareAllStrategies($weight, $destination, $strategies);

foreach ($options as $option) {
    echo "{$option['method']}:\n";
    echo "  Cost: DKK {$option['cost']}\n";
    echo "  Delivery: {$option['estimated_days']} days\n\n";
}

// Skift strategi runtime baseret på brugervalg
$calculator->setStrategy(new ExpressShippingStrategy());
$express = $calculator->calculateShipping($weight, $destination);
echo "Selected: {$express['method']} - DKK {$express['cost']}\n";

Fordele

  • +Eliminerer komplekse conditional statements
  • +Open/Closed Principle - let at tilføje nye strategier
  • +Single Responsibility - hver strategi har én specifik algoritme
  • +Runtime fleksibilitet til at skifte algoritmer
  • +Forbedret testbarhed - strategier kan testes isoleret
  • +Fremmer code reuse - strategier kan deles mellem contexts

Ulemper

  • Øget antal klasser i projektet
  • Klienter skal være opmærksomme på forskellige strategier
  • Overhead hvis strategier er meget simple
  • Kan være overkill for simple conditional scenarios

Hvornår bruges det?

  • Payment processing - forskellige payment gateways (Stripe, PayPal, MobilePay)
  • Shipping calculation - forskellige forsendelsesmetoder med forskellige priser
  • Sorting algorithms - vælg sorteringsalgoritme baseret på data størrelse
  • Validation rules - forskellige valideringsstrategier for forskellige contexts
  • Compression algorithms - vælg mellem gzip, bzip2, etc.
  • Authentication methods - OAuth, JWT, session-based, etc.

Best Practices

  • Definer klare interfaces for alle strategier
  • Brug readonly properties i PHP 8+ for immutable strategier
  • Inject strategier via constructor eller setter methods
  • Overvej Factory Pattern til at oprette strategier
  • Dokumenter forskelle mellem strategier tydeligt
  • Brug type hints og return types for type safety
  • Kombiner med Dependency Injection til loose coupling
  • Navngiv strategier beskrivende (f.eks. StandardShippingStrategy)

Quick Info

Kategori
Behavioral
Sværhedsgrad
Begynder-venlig
Relaterede Patterns
  • State Pattern - lignende struktur men fokuserer på state transitions
  • Factory Pattern - kan bruges til at vælge og oprette strategier
  • Dependency Injection - bruges ofte til at inject strategier