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.

 
 
 
 
 

Articoli correlati

    Java algoritmi di ordinamento: Bubble Sort

    Programmando, nasce spesso la necessità di ordinare le collezioni di dati o oggetti che devono poi essere manipolate. Ordinare una lista può essere utile nei casi in cui si debbano…

    Java Design Pattern: Prototype Pattern

    Andremo ora a parlare di un pattern creazionale che ci permette di "copiare con classe". Sì, anche se sembra strano, il compito fondamentale di questo pattern è copiare. Sto parlando…

    Java Design Pattern: Builder Pattern

    Andiamo oggi a parlare di un pattern creazionale che in molte situazioni può rappresentare una valida alternativa alla costruzione di oggetti mediante costruttori: il Builder Pattern. La necessità di introdurre meccanismi…

    Java Design Pattern: Factory Pattern

    Continuando il discorso sui design pattern iniziato precedentemente, andiamo oggi a vedere un altro pattern molto utilizzato: il Factory Method Pattern. Il GoF (Gang of Four Design Patterns) lo definisce così: Definisce…

    Java: Introduzione ai design pattern: Singleton

    Chiunque abbia anche una minima esperienza di programmazione, si sarà reso conto di come i problemi sianoricorrenti. Infatti troviamo spesso problemi con uno stesso pattern ma con contesti differenti. Ad esempio, un…

    Java 12, finalmente meno prolisso?

    Conosciamo tutti Java per le sue caratteristiche grazie alle quali, nonostante siano passati più di 20 anni dalla prima versione,è tutt'oggi uno dei linguaggi più studiati e più utilizzati, malgrado…

    JQuery morirà nel 2019?

    Per un po' di tempo, la centralità di JQuery è stata oggetto di dibattito tra gli sviluppatori web. Come programmatori web interessati a Javascript, eravamo curiosi di sapere che opinioni…

    45 utili siti che avresti voluto conoscere prima

    In rete sono presenti talmente tanti siti web dedicati alla sviluppo web e alla grafica, che risulta molto complicato conoscerli tutti. Oggi, vi proponiamo una lista di siti web non…

    30 Manuali di riferimento per JavaScript: jQuery, Node.js, React

    Questa lista ha lo scopo di introdurre i nuovi sviluppatori a JavaScript (jQuery, Node.js e React) e agevolare il compito degli sviluppatori più esperti. jQuery jQuery API (Official) Leggi → jQuery Cheatsheet (Devhints) Leggi → jQuery Cheat Sheet (JavaScript Toolbox) Leggi…

    Le migliori librerie JavaScript del 2018

    Dal momento che Javascript si è rivelato il linguaggio di programmazione più popolare e ampiamente utilizzato nel 2018, l'ecosistema che si sviluppa intorno ad esso sta cominciando a diventare importante. JavaScript…

    Convertire il testo in voce e viceversa con Javascript: tutorial+codice

    In questo tutorial sperimenteremo la Web Speech API: un'interfaccia browser molto potente che consente di registrare la voce umana e convertirla in testo. La useremo anche per fare il contrario: interpretare…

    I 5 Migliori Frameworks JavaScript per Applicazioni Desktop

    Non molto tempo fa era impossibile costruire un'applicazione desktop con JavaScript. Fortunatamente, questi tempi sono passati, e ora gli sviluppatori JS possono utilizzare le loro conoscenze e competenze di sviluppo…