[Design patterns in Java] State
Il pattern State si utilizza quando un oggetto modifica il proprio comportamento sulla base del suo "stato" interno.
CLASSIFICAZIONE
Il pattern State è classificato tra i pattern comportamentali.
PROBLEMA E CAMPO DI APPLICAZIONE
Quando occorre modificare il comportamento di un oggetto in base al suo stato bisogna prima di tutto memorizzare tale stato in un'opportuna variabile. Quindi utilizzare un blocco if-else per specificare le diverse opzioni possibili.
Cerchiamo di essere più chiari con un esempio pratico.
Supponiamo di voler modellare un telecomando con cui accendere e spegnere il televisore.
Prevediamo due comandi, ON e OFF, per le relative operazioni.
Premendo un ipotetico pulsante verrà effettuato lo switch dello stato, ovvero se nello stato "acceso" il televisore verrà spento e viceversa.
Una possibile implementazione potrebbe essere la seguente
public class TVRemote {
private String state = "";
public void setState(String state) {
this.state = state;
}
public void doOperation() {
if (state.equalsIgnoreCase("ON")) {
System.out.println("TV turned OFF");
} else if (state.equalsIgnoreCase("OFF")) {
System.out.println("TV turned ON");
}
}
}
Per utilizzare il telecomando occorre istanziare la classe, impostare lo stato e richiamare il metodo doOperation()
.
public class Client {
public static void main(String[] args) {
TVRemote remote = new TVRemote();
remote.setState("ON");
remote.doOperation();
remote.setState("OFF");
remote.doOperation();
}
}
Osserviamo come il client debba conoscere i diversi valori possibili per impostare correttamente lo stato.
In uno scenario più realistico doOperation()
potrebbe eseguire più step che ancora una volta il client dovrebbe conoscere in dettaglio.
Inoltre l'aggiunta di un nuovo stato obbliga a metter mano sia al blocco di selezione, che a questo punto andrebbe organizzato con una istruzione switch, ma anche al client a causa del forte accoppiamento tra le due classi.
SOLUZIONE
Il pattern State cerca di risolvere il precedente problema offrendo una soluzione basata sui concetti di State e Context.
Ma procediamo per gradi riadattando opportunamente il precedente esempio.
In primo luogo definiamo un'interfaccia State
contenente come unico metodo il doOperation()
.
public interface State {
public void doOperation();
}
Quindi realizziamo tante implementazioni quanti sono i possibili stati che l'oggetto principale può assumere.
Nel nostro caso sono ON e OFF quindi avremo due classi TVOnState
public class TVOnState implements State {
@Override
public void doOperation() {
System.out.println("TV turned OFF");
}
}
e TVOffState
public class TVOffState implements State {
@Override
public void doOperation() {
System.out.println("TV turned ON");
}
}
Naturalmente l'implementazione di doOperation()
fornirà la logica da eseguire in corrispondenza di quello specifico stato.
Ora aggiungiamo la classe TVContext
che contiene un riferimento allo stato corrente dell'oggetto che può essere impostato mediante uno specifico setter.
Anche TVContext
implementa l'interfaccia State
e in corrispondenza di doOperation()
invoca l'omonimo metodo dello stato attualmente impostato.
Il codice è veramente autoesplicativo
public class TVContext implements State {
private State tvState;
public void setState(State state) {
this.tvState = state;
}
public State getState() {
return this.tvState;
}
@Override
public void doOperation() {
tvState.doOperation();
}
}
L'impiego di questa soluzione da parte del client è molto semplice.
Occorre istanziare lo stato desiderato e il Context
, all'interno del quale impostare tale stato e richiamare il metodo doOperation()
.
In sostanza il client interagisce esclusivamente con il Context
.
Ed ecco il codice
public class Client {
public static void main(String[] args) {
TVContext context = new TVContext();
State tvOnState = new TVOnState();
State tvOffState = new TVOffState();
context.setState(tvOnState);
context.doOperation();
context.setState(tvOffState);
context.doOperation();
}
}
A questo punto appare evidente il vantaggio offerto da questo design pattern: l'aggiunta di un nuovo stato con annessa logica da eseguire consiste nel creare una nuova classe che implementa l'interfaccia State
, senza toccare il Context
.
L'unica modifica richiesta al client riguarda la creazione del nuovo oggetto-stato e l'impostazione del riferimento nel Context
.
[VIDEO]