
DESIGN PATTERNS IN JAVA | Strategy
Il pattern Strategy si utilizza per modificare a runtime l'implementazione di un metodo, scegliendo tra le diverse opzioni disponibili.
CLASSIFICAZIONE
Lo Strategy è classificato tra i pattern comportamentali.
PROBLEMA E CAMPO DI APPLICAZIONE
Quando scriviamo una classe e implementiamo un metodo il suo codice è definito una volta per tutte, per cui il suo comportamento sarà sempre lo stesso ogni volta che viene richiamato.
Ci sono delle situazioni in cui abbiamo la necessità di modificarlo a runtime in funzione delle scelte fatte dall'utente.
Un esempio classico è rappresentato dalla possibilità di salvare o esportare un contenuto digitale (testo, immagine, ecc.) in vari formati.
O ancora un sito di e-commerce che prevede diverse modalità di pagamento (bonifico, Paypal, Stripe, carta di credito).
Questo pattern ci consente di fare proprio questo, di scegliere l'algoritmo che vogliamo utilizzare in quel momento.
SOLUZIONE
Fondamentalmente abbiamo una interfaccia Strategy
con un singolo metodo algorithmInterface()
e poi una classe ConcreteStrategy
che implementa l'interfaccia e di conseguenza il relativo metodo.
Inoltre c'è una classe di contesto che serve per impostare la strategia che andremo ad utilizzare.
Nell'esempio proposto vediamo come poter salvare un documento in vari formati.
Il punto di partenza è l'interfaccia che rappresenta la nostra strategia chiamata SavingStrategy
con il metodo save(String data)
public interface SavingStrategy {
public void save(String data);
}
Una piccola osservazione: i nomi delle interfacce, delle classi e dei metodi riportati nei diagrammi UML di esempio, che si trovano in Internet o sui testi di riferimento, non sono vincolanti anzi andrebbero adattati al contesto in cui viene inserito il pattern.
Ad esempio il metodo algorithmInterface()
nel nostro caso è diventato save()
proprio ad evidenziare l'operazione che andremo ad effettuare.
Inoltre, giusto per semplificare la spiegazione, il tipo dei dati è una semplice stringa ma in contesti reali andremo a trattare oggetti più complessi.
L'interfaccia non può essere utilizzata direttamente quindi abbiamo bisogno di implementazioni concrete rappresentate da SaveAsTxt
, SaveAsPdf
, SaveAsOdf
.
Analizziamo la prima classe
public class SaveAsTxt implements SavingStrategy {
@Override
public void save(String data) {
System.out.println("Saving '" + data + "' as txt");
}
}
Ovviamente implementa l'interfaccia ed effettua l'overriding del metodo save(String data)
che per semplicità si limita a stampare un messaggio.
In un contesto reale gestirà la logica necessaria al salvataggio dei dati in formato testuale.
Un discorso simile vale per le altre classi che hanno la medesima stuttura.
Ora esaminiamo la classe di contesto che va ad utilizzare la strategy e nel nostro caso corrisponde a Document
public class Document {
private SavingStrategy strategy;
public Document() {
}
public Document(SavingStrategy strategy) {
this.strategy = strategy;
}
public void setSavingStrategy(SavingStrategy strategy) {
this.strategy = strategy;
}
public void save(String data) {
strategy.save(data);
}
}
Prima di tutto troviamo un costruttore al cui viene passata una implementazione dell'interfaccia SavingStrategy
, ovvero l'istanza di una delle precedenti classi concrete che specifica il formato di documento con cui si intende lavorare.
Tale istanza viene memorizzata nel field privato strategy
.
L'operazione può anche essere effettuata in un secondo momento sfruttando il metodo setter SettingSavingStrategy(SavingStrategy strategy)
.
L'operazione di salvataggio vera e propria viene invocata attraverso il metodo pubblico save(String data)
.
Per ultimo analizziamo il client
public class Client {
public static void main(String[] args) {
Document doc = new Document();
doc.setSavingStrategy(new SaveAsTxt());
doc.save("Data to save");
doc.setSavingStrategy(new SaveAsPdf());
doc.save("Data to save");
}
}
Semplicemente imposta la strategia che gli è stata passata all'interno della variabile strategy
e inoltre la classe Document
sfrutta un metodo save()
per acquisire i dati da salvare.
Più precisamente il metodo richiama il suo omonimo della classe Strategy
impostata e questo consente di cambiare al volo la modalità di salvataggio dei dati.
Al contrario si avrebbe un metodo save()
con una logica predefinita e immutabile a runtime.
Questo approccio consente una successiva estensione senza andare a modificare il codice preesistente, con la sola aggiunta di nuove implementazioni dell'interfaccia SavingStrategy
.
Stesso discorso se occorre modificare proprio il meccanismo di salvataggio dei dati. L'impatto è limitato alla sola classe interessata.
[VIDEO]