Lo strategy è tra i design pattern comportamentali più utilizzati nella programmazione ad oggetti (OOP) soprattutto in approcci MVC.
Vediamo come implementarlo correttamente in PHP partendo dalla definizione data dalla Gang of Four (GoF).

Lo Strategy pattern definisce una famiglia di algoritmi, li incapsula e li rende interscambiabili tra di loro. Strategy permette di variare gli algoritmi indipendentemente dai client che li usano.

Il design pattern Strategy è utile quando si desidera selezionare l’algoritmo da utilizzare in fase di esecuzione ed è composto da tre elementi fondamentali:

  • L’interfaccia o la classe astratta “Strategy”: definisce il contratto che deve essere implementato dalle classi che rappresentano le diverse strategie di algoritmo. L’interfaccia o la classe astratta può includere un o più metodi, che rappresentano le operazioni che l’algoritmo deve essere in grado di eseguire.
  • Le classi concrete delle strategie: queste classi implementano l’interfaccia o la classe astratta “Strategy” e rappresentano le diverse implementazioni degli algoritmi.
  • La classe “Context”: rappresenta il contesto in cui viene eseguito l’algoritmo e che utilizza una delle strategie disponibili per l’esecuzione. La classe “Context” deve accettare un’istanza di una delle classi concrete delle strategie attraverso il costruttore e/o un metodo setter.

Scenario di utilizzo

Il desgin pattern Strategy è utile in situazioni in cui è necessario avere la possibilità di cambiare il comportamento di un’operazione in modo flessibile e senza dover modificare il codice dell’operazione stessa.

Esempi concreti di impiego possono essere:

  • Nel controller di un framework MVC, è possibile utilizzare il pattern Strategy per incapsulare la logica di business associata a un’azione specifica. Ad esempio, se un’azione richiede la validazione dei dati di input, è possibile utilizzare una classe di validazione Strategy per gestire questo compito in modo indipendente dalla logica di business principale.
  • Nel modello di un framework MVC, è possibile utilizzare il pattern Strategy per gestire la connessione ai diversi database utilizzati dall’applicazione. In questo modo, è possibile rendere la scelta del database dinamica e intercambiabile a runtime.
  • Nel template di un framework MVC, è possibile utilizzare il pattern Strategy per definire diverse strategie di rendering a seconda del tipo di visualizzazione richiesta. Ad esempio, è possibile utilizzare una classe di rendering HTML per le visualizzazioni web e una classe di rendering JSON per le API REST.

Esempio di pattern Strategy in PHP

Supponiamo di dover calcolare il prezzo di un prodotto in un negozio online, ma il prezzo può variare in base a diversi fattori, come il tipo di sconto, le imposte applicabili e le tariffe di spedizione.

Invece di creare un metodo per ogni combinazione possibile di fattori, possiamo utilizzare il design pattern Strategy per creare un insieme di algoritmi, o strategie, che possono essere scelti in fase di esecuzione in base ai fattori specifici.

Vediamo ora il codice di base per implementare questo pattern.

// Creazione dell'interfaccia della strategia
interface PricingStrategy {
    public function calculatePrice($product);
}

// Implementazione della prima strategia: nessuno sconto
class NoDiscountStrategy implements PricingStrategy {
    public function calculatePrice($product) {
        return $product->getPrice();
    }
}

// Implementazione della seconda strategia: sconto del 10%
class TenPercentDiscountStrategy implements PricingStrategy {
    public function calculatePrice($product) {
        return $product->getPrice() * 0.9;
    }
}

// Implementazione della terza strategia: sconto del 20%
class TwentyPercentDiscountStrategy implements PricingStrategy {
    public function calculatePrice($product) {
        return $product->getPrice() * 0.8;
    }
}

// Implementazione della classe prodotto
class Product {
    private $price;
    private $pricingStrategy;
    
    public function __construct($price, $pricingStrategy) {
        $this->price = $price;
        $this->pricingStrategy = $pricingStrategy;
    }
    
    public function getPrice() {
        return $this->pricingStrategy->calculatePrice($this);
    }
    
    public function setPricingStrategy($pricingStrategy) {
        $this->pricingStrategy = $pricingStrategy;
    }
}

// Utilizzo del codice
$product = new Product(100, new NoDiscountStrategy());
echo $product->getPrice(); // output: 100

$product->setPricingStrategy(new TenPercentDiscountStrategy());
echo $product->getPrice(); // output: 90

$product->setPricingStrategy(new TwentyPercentDiscountStrategy());
echo $product->getPrice(); // output: 80

In questo esempio, abbiamo creato un’interfaccia PricingStrategy che definisce un metodo calculatePrice(), che viene implementato dalle classi NoDiscountStrategy, TenPercentDiscountStrategy e TwentyPercentDiscountStrategy.

Abbiamo quindi creato una classe Product che accetta un oggetto PricingStrategy nel suo costruttore e ha un metodo getPrice() che utilizza la strategia selezionata per calcolare il prezzo del prodotto.

Infine, abbiamo creato un oggetto Product con un prezzo di 100 e senza sconto, e abbiamo utilizzato il metodo setPricingStrategy() per selezionare le altre strategie di sconto e calcolare il prezzo del prodotto in base ad esse.

Vantaggi del design pattern strategy

  • Consente di isolare la logica di business in classi separate, rendendo il codice più modulare e facile da testare.
  • Permette di sostituire gli algoritmi utilizzati in modo semplice e senza dover modificare il codice esistente.
  • Favorisce l’estendibilità del codice, poiché nuovi algoritmi possono essere facilmente aggiunti senza dover modificare la struttura esistente.

Svantaggi del design pattern strategy

  • L’uso del pattern Strategy può portare a un aumento della complessità del codice, poiché richiede la creazione di molte classi aggiuntive.
  • Il pattern Strategy potrebbe non essere necessario in tutti i casi e potrebbe rappresentare una soluzione eccessiva in alcune situazioni.

Conclusioni

Il design pattern Strategy può essere una soluzione potente per creare codice modulare e flessibile, ma deve essere utilizzato con cautela e solo quando è effettivamente necessario.

Per un panoramica generale consiglio di leggere l’articolo introduttivo sui design patterns.