Java Design Pattern: Strategy Pattern

Quando utilità e semplicità si uniscono


Uno dei pattern che gode di una notevole popolarità ed è al contempo piuttosto semplice è lo Strategy Pattern.

Membro della famiglia dei pattern comportamentali, ha il compito di gestire algoritmi, relazioni e responsabilità tra classi. Il GoF lo definisce come:

Definisce una serie di algoritmi incapsulati che possono essere scambiati per ottenere comportamenti specifici.

Vediamo il diagramma UML:

Strategy Pattern UML

Si può notare come il Context, che può essere immaginato come qualsiasi entità abbia bisogno di un comportamento "dinamico", sia composto da una Strategy.

Perchè Strategy è un'interfaccia? Per il semplice fatto che le ConcreteStrategy implementeranno questa interfaccia in maniera tale che, nel momento in cui si decide di cambiare l'implementazione dei metodi delle classi concrete, la struttura del Context non muterà.

Parlando di classi già presenti nelle varie librerie Java, i java.awt.Container component sono un esempio di Strategy Pattern, infatti il LayoutManager agisce come classe Strategy, e le classi come BorderLayout e FlowLayout implementano LayoutManager implementando il metodo addLayoutComponent(). Le diverse implementazioni si diversificano per il modo e la posizione dell'oggetto che verrà inserito nel Container. La classe Container contiene l'oggetto LayoutManager.

Altri esempi sono:

  • java.util.Comparator#compare() chiamato da Collection.sort();
  • javax.servlet.http.HttpServlet: metodo service() con tutti i relativi metodi doXXXX() che accettano HttpServletRequest e HttpServletResponse come argomenti;
  • javax.servlet.Filter#doFilter();

Vediamo in dettaglio le singole componenti del pattern.

  • Strategy: è l'interfaccia che dichiara la famiglia di algoritmi e che viene utilizzata da Context per invocare un algoritmo concreto;
  • Context: classe di contesto che invoca le ConcreteStrategy. Può esporre un'interfaccia per permettere alle ConcreteStrategy di accedere ad eventuali strutture dati interne;
  •  ConcreteStrategy: sono l'implementazione degli algoritmi che espone Strategy;

Cerchiamo di capire la struttura generale del pattern:

Strategy

public interface Strategy{
     public void execute(paremeters);
}

ConcreteStrategy

public class ConcreteStrategyA implements Strategy{
  @Override
  public void execute(parameters){
   //implementazione
  }
}
public class ConcreteStrategyB implements Strategy{
  @Override
  public void execute(parameters){
   //implementazione
  }
}

Context

public class SortingContext {   
  private Strategy strategy;
  public void setMethod(Strategy strategy) {
   this.strategy = strategy;
  }   
  public Strategy getStrategy() {
   return strategy;   
  }   
  public void doMethod(paremters){
   strategy.execute(parameters);   }
}

Precisiamo il fatto che dando una struttura generale, l'interfaccia espone un solo metodo e quindi le classi che la implementano ne sovrascriveranno solo uno. Si può anche estendere la struttura esponendo più metodi astratti ed il funzionamento rimarrebbe invariato. Il codice sarebbe solo leggermente più lungo, ma non necessariamente complesso.

Vediamo uno schema di come un client mette in azione lo Strategy Pattern:

Strategy pattern in action

Ecco un esempio che utilizza i diversi algoritmi di ordinamento applicando lo Strategy Pattern:

Strategy

public interface SortingStrategy{
     public void sort(int[] v);
}

ConcreteStrategy

public class SelectionSort implements SortingStrategy{
     @Override
     public void sort(int[] v){
        System.out.println("Selection Sort!");
        int first;
        int temp;
        for (int index = v.length - 1; index > 0; index--) {
           first = 0;
           for (int j = 1; j <= i; j++) {
           if (v[j] > v[first])
              first = j;
           }
        temp = v[first];
        v[first] = v[index];
        v[index] = temp;
        }
     System.out.println(Arrays.toString(v));
  }
}

public class InsertionSort implements SortingStrategy {
  @Override
  public void sort(int[] v) {
     System.out.println("Insertion Sort!");
     for (int index = 1; index < v.length; index++) {
        int temp = v[index];
        int j;
        for (j = index - 1; (j >= 0) && (v[j] > temp); j--) {
           v[j + 1] = v[j];
        }
        v[j + 1] = temp;
     }
  System.out.println(Arrays.toString(v));
  }
}

Context

public class SortingContext {
  private SortingStrategy strategy;
  public void setSortingMethod(SortingStrategy strategy) {
     this.strategy = strategy;
  }
  public SortingStrategy getStrategy() {
     return strategy;
  }
  public void sortNumbers(int[] v){
     strategy.sort(v);
  }
}

Capiamo il codice

  • SortingStrategy: ha il comportamento classico dell'interfaccia Strategy descritta prima, infatti espone un metodo che rappresenta l'algoritmo da implementare;
  • SelectionSort/InsertionSort: svolgono il ruolo del ConcreteContext. Questo perchè implementando SortingStrategy effettuano l'overriding del metodo, definendo un'implementazione dell'algoritmo di ordinamento desiderato;
  • SortingContext: svolge il ruolo di Context, esponendo un oggetto SortingStrategy che successivamente diventerà un'istanza di una delle classi concrete;

Ma perchè ricorrere allo Strategy Pattern?

Capita talvolta di avere situazioni piuttosto complesse dove utilizzando meccanismi di ereditarietà, si avrebbero gerarchie piuttosto lunghe e contorte. Questo pattern permette di ridurre notevolmente la complessità del codice rendendolo più lineare. D'altro canto, tutti i client che utilizzano la nostra classe “Context” devono conoscere lo Strategy più indicato da utilizzare.

Una domanda che ora potrebbe sorgere spontanea è: "O, mi hai fatto vedere un esempio canonico con gli algoritmi di ordinamento. Praticamente, dove viene utilizzato?".

I casi d'uso sono molteplici. Possiamo pensare a software di compressione dati, i quali ci permettono di scegliere tra diversi formati di compressione. Un altro esempio è una piattaforma di pagamento dove ipoteticamente si può scegliere il metodo di pagamento da utilizzare e via discorrendo.

Questi casi, come molti altri, fanno uso dello Strategy Pattern.

Va precisato che in molti casi, organizzare bene il pattern potrebbe non essere banale.

Qualche osservazione

In definitiva, abbiamo definito una struttura generale del modello, dado la costruzione generale delle varie componenti dandone poi un esempio pratico.

Secondo me, per un qualsiasi programmatore che si voglia affacciare ad una programmazione professionale, questo è uno tra i must da sapere. Permette di linearizzare tante situazioni che sfruttando i comuni meccanismi che ci vengono insegnati a volte alle scuole superiori, come l'ereditarietà, sarebbero nettamente più complicate.

Parlando di un minimo di informatica teorica, questo pattern sfrutta pesantemente il meccanismo del polimorfismo messo a disposizione dalla presenza delle interfacce che sfruttiamo come Strategy. L'importanza di avere un'interfaccia generica, come accennato prima, è quella di poter cambiare il comportamento delle ConcreteStrategy senza andare a modificare la struttura della classe stessa, infatti sfruttiamo anche la possibilità di avere un tipo apparente diverso dal tipo reale della classe. Infatti, come sappiamo in Java è lecito dichiarare, ad esempio, un arraylist come:

List list = new ArrayList(100);

Questo principio viene utilizzato quando andiamo a dichiarare un oggetto Strategy nel context e successivamente lo istanziamo come istanza di una ConcreteStrategy, che implementa Strategy.

In definitiva, come detto prima, è qualcosa da ssapere, che può tornare veramente utile in molte situazioni.

 
 
Alessio Mungelli

Alessio Mungelli

Computer Science student at UniTo (University of Turin), Network specializtion, blogger and writer. I am a kind of expert in Java desktop developement with interests in AI and web developement. Unix lover (but not Windows hater). I am interested in Linux scripting. I am very inquisitive and I love learning new stuffs.

 
 
 

Articoli correlati

Un approccio a Java: switch statement

Ciao a tutti e bentornati! Dopo una pausa, torniamo oggi con un'altra parte del corso introduttivo alla programmazione, parlando di switch statement, conosciuto anche come costrutto di selezione multipla.  Intuizione L'idea dello switch statement…

Un approccio a Java: Il ciclo while

Ciao a tutti e bentornati! Dopo aver fatto una breve, ma corposa, introduzione sui cicli, andiamo oggi a vedere finalmente le prime implementazioni che utilizzano quello che abbiamo definito ciclo precondizionale. In Java, come…

Un approccio a Java: I cicli - Introduzione

Ciao a tutti e bentornati! Sino ad ora, abbiamo parlato di variabili e di strutture di selezione, andando a considerare alcuni degli aspetti fondamentali di questi due concetti. Teoricamente, per…

Un approccio a Java: strutture di selezione - casi d'uso

Ciao a tutti e bentornati! Sino ad ora ci siamo preoccupati di fare una carrellata quanto più completa riguardo i concetti fondamentali di cui abbiamo bisogno per approcciarci all'utilizzo delle…

Un approccio a Java: operatori booleani

La volta precedente, abbiamo ampiamente parlato delle variabili booleane, cercando di delineare quali siano le principali operazioni che si possono effettuare proprio a livello pratico.  Di tutti i casi esaminati, non abbiamo…

Un approccio a Java: le variabili booleane

Ciao a tutti e bentornati! La volta precedente, ho fatto un'introduzione alle strutture condizionali, definendo il loro funzionamento. Prima di poter dare qualche esempio pratico, è necessario chiarire in che modo ci…

Un approccio a Java: strutture condizionali

Ciao a tutti e bentornati! Le volte precedenti abbiamo introdotto il concetto di variabile, tentando di definire alcuni concetti basilari a riguardo.  Alcune situazioni fanno però intuire come il solo concetto…

Un approccio a Java: Le variabili - caso d'uso

Ciao a tutti amici e bentornati! Dopo l'introduzione fatta sulle variabili, cerchiamo di analizzare alcune criticità che si possono presentare in situazioni alquanto comuni. Partiamo quindi analizzando degli esempi pratici.  Esempio 1: divisione…

Un approccio a Java: Le variabili

Ciao a tutti e bentornati! Quest'oggi inizieremo un percorso che ci porterà a studiare, ed eventualmente ripassare, quelle che sono le basi della programmazione. Inizieremo parlando di variaibli. Introduzione Chiunque voglia approcciarsi al…

Hashmap con concatenamento: liste di trabocco

In questa breve serie di articoli andremo a vedere com'è possibile realizzare in C la struttura dati Hashmap. Nell'implementazione andremo ad usare le liste doppiamente concatenate come strutture dati ausiliarie. Andiamo…

Java Strutture Dati: Liste Concatenate

Con il 2020 andiamo ad esaminare un nuovo aspetto della programmazione: le strutture dati. Spesso capita a tutti di utilizzare strutture messe a disposizione dai vari linguaggi di programmazione. L'obiettivo sarà…

Java Algoritmi di Ordinamento: Selection Sort

Andiamo oggi ad analizzare in dettaglio un algoritmo di ordinamento non molto efficiente ma piuttosto utilizzato in diversi ambiti. Stiamo parlando del Selection Sort. Vediamo meglio in dettaglio. Intuizione L'idea alla base è quella…