Home > GOF Pattern, java > GoF Patterns: Strategy

GoF Patterns: Strategy

27 luglio 2012

Translate in English with Google Translate
In questo articolo tratterò il pattern Strategy anche detto Policy

Motivazione
Si tratta di un pattern comportamentale basato su oggetti e viene utilizzato per definire una famiglia di algoritmi, incapsularli e renderli intercambiabili. Il client definisce l’algoritmo da utilizzare, incapsulandolo in un contesto, il quale verrà utilizzato nella fase di elaborazione. Il contesto detiene i puntamenti alle informazioni necessarie al fine della elaborazione, cioè dati e funzione: solita equazione y=f(x)!

Gli algoritmi definiscono il modo in cui vengono elaborate le informazioni. Possiamo creare delle classi di algoritmi che implementano in modo diverso uno stesso algoritmo oppure possiamo creare delle nuove classi di algoritmi. Per esempio nel caso di un problema di ordinamento, possiamo creare diverse classi che implementano l’algoritmo BubbleSorting ma in modo diverso, così come possiamo creare delle classi di ordinamento basate su nuovi algoritmi, per esempio SelectionSorting oppure InsertionSorting.
L’utilizzo di questo pattern consente al client di poter scegliere quale algoritmo e quale implementazione utilizzare senza dover conoscere i loro dettagli implementativi.

Partecipanti e Struttura
Questo pattern è composto dai seguenti partecipanti:

  • Strategy: dichiara una interfaccia che verrà invocata dal Context in base all’algoritmo prescelto
  • ConcreteStrategy: effettua l’overwrite del metodo del Context al fine di ritornare l’implementazione dell’algoritmo
  • Context: detiene le informazioni di contesto (dati ed algoritmo da utilizzare) ed ha il compito di invocare l’algoritmo

Vediamo come si presenta il Pattern Strategy utilizzando il Class Diagram in UML:

Strategy Pattern - Struttura

Strategy Pattern – Struttura

Tale pattern presenta i seguenti vantaggi/svantaggi:

  • Famiglie di algoritmi correlati: gli algoritmi possono essere correlati in base ad una catena di ereditarietà ed in questo modo è possibile mettere a fattor comune dei loro comportamenti al fine del riuso.
  • Un’alternativa all’ereditarietà: è possibile estendere il Context per specializzare il suo comportamento ma si rischia di legare troppo il Context con lo Strategy
  • Le strategie eliminano i blocchi condizionali: le strategie consentono di eliminare i blocchi condizionali che determinano il comportamento voluto.
  • Scelta dell’implementazione: i Client possono scegliere quale classe di Strategy usare sulla base della occupazione di memoria, velocità di esecuzione ecc
  • I Client devono conoscere le diverse Strategy disponibili: lo svantaggio di questo pattern è che i client devono conoscere le strategie esistenti e le loro differenze al fine di poter prendere una decisione sensata.
  • Collaborazione tra Strategy e Context dispendiosa: le strategie possono richiedere maggiori o minori informazioni di contesto di quello disponibili dal Context pertanto occorre definire una modalità di scambio informazioni efficiente tra Context e Strategy. Un modo per evitare l’overloading dei metodi nelle classi di Strategy per ricevere parametri diversi, è di inserire i parametri nel Context per poi passarlo alla classe Strategy.
  • Aumento del numero di oggetti: spesso le strategie proliferano gli oggetti dell’applicazione pertanto si potrebbe applicare il pattern Flyweight per ridurre l’occupazione di memoria assicurandosi di non mantenere lo stato (esterno e non interno) della strategia tra diverse invocazioni.

Implementazione
Come esempio implementiamo il caso di un algoritmo di ordinamento. Possiamo utilizzare diversi algoritmi, dai più semplici ai più complessi: Bubble Sort, Selection Sort, Insertion Sort, Merge Sort, Quick Sort ecc. Lasciamo all’utente la libertà di scelta dell’algoritmo da utilizzare ed inoltre lui definirà i dati da essere oggetto dell’ordinamento.

Vediamo come si presenta il pattern in UML in base all’esempio:

Strategy Pattern - Sorting

Strategy Pattern – Sorting

Definiamo la nostra classe di contesto, che rappresenta il legame tra il Client e l’interfaccia dell’algoritmo di ordinamento. In sede di instanza, il costruttore ElementList() definisce una lista vuota che ospiterà i valori oggetto dell’ordinamento. Successivamente verranno caricati i valori da ordinare tramite il metodo addElement(int… values) ed infine verrà definito l’algoritmo di ordinamento da utilizzare tramite il metodo setSortAlgorithm( SortAlgorithm sortAlgorithm). Per ultimo il metodo getSortedList() che invoca l’algoritmo concreto passandogli i dati da ordinare.

package patterns.strategy.sort;

import java.util.ArrayList;
import java.util.List;

public class ElementList {

    private List elements;
    private SortAlgorithm sortAlgorithm;

    public ElementList(){
        elements = new ArrayList();
    }

    public void addElement(int... values){
        for(int value: values)
            elements.add( value );
    }

    public void setSortAlgorithm( SortAlgorithm sortAlgorithm){
        this.sortAlgorithm = sortAlgorithm;
    }

    public List getSortedList(){
        return sortAlgorithm.sort( elements );
    }
}

L’interfaccia SortAlgorithm dichiara il metodo sort() che prenderà in ingresso una lista non ordinata per restituire una ordinata.

package patterns.strategy.sort;

import java.util.List;

public interface SortAlgorithm {

    public List sort(List unSortedList);

}

Adesso passiamo in rassegna le classi che implementano i singoli algoritmi di ordinamento, ovviamente in questa sede non li ho implementati in quanto non era il mio obiettivo, ma spero di trattarli a breve.

package patterns.strategy.sort;

import java.util.List;

public class BubbleSortAlgorithm implements SortAlgorithm {

    @Override
    public List sort(List unSortedList) {
        System.out.println("BubbleSortAlgorithm");
        //TODO algoritmo da implementare, fuori contesto
        return null;
    }

}

Di seguito la classe che implementa l’algoritmo Selection Sort

package patterns.strategy.sort;

import java.util.List;

public class SelectionSortAlgorithm implements SortAlgorithm {

    @Override
    public List sort(List unSortedList) {
        System.out.println("SelectionSortAlgorithm");
        //TODO algoritmo da implementare, fuori contesto
        return null;
    }

}

E la classe che implementa l’algoritmo Insertion Sort:

package patterns.strategy.sort;

import java.util.List;

public class InsertionSortAlgorithm implements SortAlgorithm {

    @Override
    public List sort(List unSortedList) {
        System.out.println("InsertionSortAlgorithm");
        //TODO algoritmo da implementare, fuori contesto
        return null;
    }

}

Adesso realizziamo la classe Client che utilizza la classe di contesto per: inizializzare il vettore, aggiungere gli elementi, settare l’algoritmo di ordinamento ed infine recuperare la lista ordinata.

package patterns.strategy.sort;

import java.util.List;

public class Client {

    public static void main(String[] args) {
        ElementList contextElements = new ElementList();
        contextElements.addElement(new int[]{3,2,4,3,6,5});
        contextElements.setSortAlgorithm(new BubbleSortAlgorithm());
        List sortedList = contextElements.getSortedList();
    }
}

Vediamo di seguito l’esecuzione del codice e l’output…simbolico…🙂

$JAVA_HOME/bin patterns.strategy.sort.Client
BubbleSortAlgorithm

Nella libreira del JDK il pattern Strategy viene utilizzato nelle librerie grafiche (awt e swing) per esempio per definire la tipologia di Border da applicare al JPanel. Il Context è definito da JPanel, in particolare da JComponent, che detiene il puntatore alla “strategia” del bordo da utilizzare.

Vediamo di seguito uno stralcio del Class Diagram delle classi:

Strategy Pattern  - Border

Strategy Pattern – Border

Di seguito un Client che effettua l’invocazione al contesto, JPanel, che setta tramite JComponent, il Border da usare per poi visualizzarlo.

package patterns.strategy.swing;

import java.awt.Color;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;

public class Client extends JFrame {

    public static void main(String[] args) {
        JFrame frame = new Client();
        frame.setBounds(400, 300, 300, 200);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setVisible(true);
    }

    public Client() {
        super("Strategy Pattern");
        JPanel jPanel = new JPanel();
        jPanel.setBorder(new LineBorder(Color.RED));
        getContentPane().add(jPanel);
    }
}

L’output è il seguente:

Line Border

Line Border

Se modifichiamo la strategia ed utilizziamo uno dei Border disponibili, per esempio TitledBorder, dobbiamo settarlo nel JPanel, come di seguito:

...
        jPanel.setBorder(new TitledBorder("Hello Border!"));
...

L’output è il seguente:

Titled Border

Titled Border

Categorie:GOF Pattern, java
  1. Gino
    27 luglio 2014 alle 4:55 PM

    Spiegato benissimo, come gli altri pattern che hai trattato.

  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: