Index

Related


Introduzione

Strategy è un design pattern comportamentale che permette di definire una famiglia di algoritmi, metterli ciascuno in una classe separata e rendere i loro oggetti interscambiabili.

Intercambialità

Rendere degli algoritmi intercambiabili significa che è possibile sostituire un algoritmo con un altro della stessa famiglia senza modificare il contesto che utilizza l’algoritmo. Quindi la tipologia di Input e Output, e la logica interna degli algoritmi deve essere consistente.


Funzionamento

ContextLa Classe che utilizza l’oggetto strategy. In particolare è responsabile della gestione degli input e output dell’oggetto strategy.
Strategy (Interface)L’interfaccia che definisce il contratto (ovvero l’algoritmo generale) che le strategie concrete devono eseguire.
Concrete StrategiesLe classi che implementano l’Interfaccia Strategy. Ogni strategia concreta fornisce una implementazione specifica dell’algoritmo.
ClientLa lasse che utilizza la classe Context. È la classe che inizia l’esecuzione dell’algoritmo chiamando la classe di contesto.

Implementazione

  1. Nella classe contesto, identificare un algoritmo soggetto a frequenti modifiche. Può anche trattarsi di un condizionale massivo che seleziona ed esegue una variante dello stesso algoritmo in fase di esecuzione.
  2. Dichiarare l’interfaccia della strategia comune a tutte le varianti dell’algoritmo.
  3. Uno per uno, estrarre tutti gli algoritmi nelle loro classi. Tutte devono implementare l’interfaccia della strategia.
  4. Nella classe context, aggiungere un campo per memorizzare un riferimento a un oggetto strategia. Fornire un setter per sostituire i valori di tale campo. Il contesto deve lavorare con l’oggetto strategia solo attraverso l’interfaccia strategia. Il contesto può definire un’interfaccia che consenta alla strategia di accedere ai suoi dati.
  5. I clienti del contesto devono associarlo a una strategia adeguata, che corrisponda al modo in cui si aspettano che il contesto svolga il suo lavoro principale.

Esempio

Questo esempio consiste nell’implementazione dei metodi utilizzati per effettuare dei pagamenti all’interno di un app.

Partiamo da uno Stato iniziale dove non viene applicato lo Strategy Pattern,

Stato iniziale

Partiamo dallo Stato iniziale dove tutto è in unica classe, in questo caso questo metodo si occupa sia dei pagamenti via carta di credito che via PayPall.

Codice

public public class PaymentService {
   private int cost;
   private boolean includeDelivery; 
 
   public void procceesPayment(String paymentMethod){
       if("CreditCard".equals(paymentMethod)){
           // pop-up to collect credit card info
           // process payment
           System.out.println("Paying " + getTotalCost() + " with Credit Card");
       } else if("PayPal".equals(paymentMethod)){
           // pop-up to collect PayPal info
           // process payment
           System.out.println("Paying " + getTotalCost() + " with PayPal");
       }
   }
 
   public int getTotalCost(){
       return cost + (includeDelivery ? 10 : 0);
   }
}

Problemi

  • Codice poco leggibile.
  • Difficile da mantenere infatti aggiungere altri metodi di pagamento, richiede di modificare un metodo funzionante rischiando di inserire dei bug. In particolare inserire un altro metodo di pagamento in questo codice significherebbe estendere la catena di if else.
  • Non rispetta il Closed Principle.
  • Non rispetta il Single Responsibility Principle.

Applicare Strategy Pattern

Ora vediamo come risolvere i problemi dell’esempio stato iniziale utilizzando lo Strategy Pattern.

Soluzione

  1. Definiremo un’interfaccia comune, PaymentStrategy, per tutti i metodi di pagamento.
  2. Implementeremo classi concrete di metodi di pagamento che aderiscono a questa interfaccia.
  • Nota bene facendo ciò rendiamo le classi che implementano i metodi di pagamento intercambiabili tra loro.
  1. Creeremo una classe di contesto, PaymentService, che utilizza l’interfaccia PaymentStrategy per elaborare i pagamenti.

1) Creazione dell’interfaccia Strategy

Per rendere le classi dei metodi di pagamento intercambiabili quest’ultime dovranno implementare un interfaccia (Strategy) che:

  • stabilisce la struttura della logica interna delle classi.
  • stabilisce gli input e output che le accomunano le classi.

2) Implementazione dell’interfaccia Strategy

Dopo aver definito l’interfaccia, dobbiamo assicurarci che venga implementata nelle classi dei metodi di pagamento.

3) Implementazione del classe Context

Una volta implementata l’interfaccia Strategy, possiamo utilizzarla nel classe di contesto ovvero la classe che utilizza l’interfaccia Strategy, in questo caso PaymentService.

È importante notare che questo metodo oramai non ha più visibilità sul come la logica di pagamento avvenga, infatti questa responsabilità è stata delegata a tutti i metodi che implementano l’interfaccia strategy.

Questo è importante perché ci permette di implementare altri metodi di pagamento senza dovere toccare il resto del codice.

Esempio di utilizzo (client)

Vediamo un esempio di un possibile client che utilizza le classi utilizzate per implementare PaymentService utilizzando lo Strategy Pattern.

Esempio Client

public class Main {
   public static void main(String[] args) {
       // Creo un oggetto PaymentService
       PaymentService paymentService = new PaymentService();
 
       // Utilizzo la strategia di pagamento con carta di credito
       paymentService.setStrategy(new PaymentByCreditCard());
       paymentService.processOrder(100);
 
       // Utilizzo la strategia di pagamento con PayPal
       paymentService.setStrategy(new PaymentByPayPal());
       paymentService.processOrder(200);
   }
}

Sources