STATE DESIGN PATTERN: come evitare troppi if condizionati

Giuseppe Toto
Forgiatore di materia virtuale
4 min readSep 10, 2014

--

Spesso ci troviamo a costruire una classe che cambia il suo comportamento in base a una combinazione di valori di certe variabili. In genere quindi costruiamo la logica attraverso dei semplici if o switch abbastanza rudimentali. Nella nostra classe siamo così spinti a introdurre un sacco di logica condizionale, che però si traduce in un risultato poco comprensibile e difficilmente manutenibile.

Per affrontare in modo efficiente questo problema, i GoF (riferito ai 4 autori del del libro Design Patterns: Elements of Reusable Object-Oriented Software) suggeriscono l’uso del design patter a stati: consente ad un oggetto di modificare il suo comportamento quando il suo stato interno cambia.

Cerchiamo subito di entrare nel dettaglio con un esempio di riferimento.

Supponiamo di avere un orologio che possiede due pulsanti: MODE E CHANGE (queste saranno le nostre azioni). Il tasto MODE permetterà di cambiare lo stato del nostro orologio in :

  • VISUALIZZAZIONE NORMALE
  • MODICHE DELLE ORE
  • MODIFICA DEI MINUTI

Quando premiamo il tasto CHANGE vogliamo che il nostro OROLOGIO subisca un comportamento diverso in base allo stato in cui si trova:

  • STATO “VISUALIZZAZIONE NORMALE -> AZIONE CHANGE -> SI ACCENDE LA LUCE
  • STATO “MODIFICHE DELLE ORE” -> AZIONE CHANGE -> INCREMENTO DI +1 L’ORA
  • STATO “MODIFICA DEI MINUTI” -> AZIONE CHANGE -> INCREMENTO DI +1 I MINUTI

Quindi il comportamento associato all’azione CHANGE cambia in baso allo stato in cui l’orologio si trova.

Se dovessi ragionare attraverso un insieme di condizioni logiche mi troverei a scrivere qualcosa di questo tipo:

function pressButtonChange(){
if(clockstate == NORMAL_DISPLAY)
activeLightScreen();
else if(clockstate == UPDATING_HOURS)
hours++;
else if(clockstate == UPDATING_MINUTES)
minutes++;
}
}

Come già detto il problema di questo codice è che si rende difficile la manutenzione, perchè la creazione di nuovi stati comporta la modifica di tutte le operazioni. Inoltre non si tiene una visione globale del codice, in modo da capire come agisce l’oggetto (l’orologio in questo caso), a seconda dello stato in cui si trova.

Vediamo quindi come poter implementare un’automa a stati finiti tramite il Pattern State.

Struttura del Pattern

gofState

Questo diagramma di classi rappresenta come è strutturato il pattern state:

  • Context
  • un oggetto che eseguirà determinazione operazioni quando si troverà in un particolare stato. In sostanza è l’oggetto reale a cui si potrà accedere dal client. Possederà un riferimento allo stato corrente.
  • State:
  • Questa è un’interfaccia o una classe astratta che definisce il comportamento comune associato a tutti i possibili stati
  • ConcreteState
  • Ogni stato viene rappresentata con una classe concreta. Rappresenta quindi uno stato del Contesto e implementa l’interfaccia/classe astratta State. Ogni ConcreteState condivide uno stesso Context e quindi avrà un suo specifico riferimento in modo da poter poi cambiare lo stato a lui associato dopo che determinate operazioni sono state eseguite.

Applichiamo il pattern al nostro esempio

esempio design pattern state2

Vi lascio il codice di ogni classe. Per maggiori approfondimenti date uno sguardo ai riferimenti in calce.

public class Clock {
private ClockState clockState;
public int hr, min;

public Clock() {
clockState = new NormalDisplayState(this);
}

public void setState(ClockState cs) {
clockState = cs;
}

public void modeButton() {
clockState.modeButton();
}

public void changeButton() {
clockState.changeButton();
}

public void showTime() {
System.out.println("Current time is Hr : " + hr + " Min: " + min);
}
}
public abstract class ClockState {
protected Clock clock;

public ClockState(Clock clock) {
this.clock = clock;
}

public abstract void modeButton();
public abstract void changeButton();
}
public class NormalDisplayState extends ClockState {
public NormalDisplayState(Clock clock) {
super(clock);
}

public void modeButton() {
clock.setState(new UpdatingHrState(clock));
}

public void changeButton() {
System.out.print("LIGHT ON: ");
clock.showTime();
}
}
public class UpdatingHrState extends ClockState {
public UpdatingHrState(Clock clock) {
super( clock );
}
public void modeButton() {
clock.setState( new UpdatingMinState( clock ) );
}
public void changeButton() {
clock.hr++;
if(clock.hr == 24)
clock.hr = 0;
clock.showTime();
}
}
public class UpdatingMinState extends ClockState {
public UpdatingMinState(Clock clock) {
super( clock );
}
public void modeButton() {
clock.setState( new NormalDisplayState( clock ) );
}
public void changeButton() {
clock.min++;
if(clock.min == 60)
clock.min = 0;
clock.showTime();
}
}

CONCLUSIONI

Grazie al state design patter possiamo modellare il comportamento del nostro oggetto in modo altamente manuntenibile. Ogni stato quindi rappresenta un insieme di condizioni (ovvero un insieme di valori di certe variabili che si possono verificare in un dato istante). Ogni stato cambia quindi il comportamento dell’oggetto quando questo si verifica.

Possiamo così avere una visione globale di come il nostro oggetto si comporta a seconda degli stati in cui si trova e possiamo aggiungere nuovi stati o cambiare il passaggio di uno stato all’altro senza dover necessariamente modificare la logica racchiusa negli altri stati.

RIFERIMENTI:

--

--