giovedì 3 gennaio 2008

Implementazione di una macchina a stati sequenziale in JavaScript

Come macchina a stati sequenziale potremmo definire una normale macchina a stati (che supporremo finiti) in cui le uniche transizioni permesse siano quelle tra stati adiacenti. Ad esempio, dallo stato 3 è possibile passare solo allo stato 2 od allo stato 4.

Nelle interfacce uomo-macchina è possibile associare questo tipo di macchine a stati al concetto di wizard, ovvero un tipo di interfaccia che guidi l'utente passo per passo nello svolgimento di un compito.

I wizard sono strumenti abbastanza potenti, perché facilitano di molto l'interazione con gli utenti, e quindi se ne fa largo uso anche durante la realizzazione di interfacce web.

Durante la mia prima avventura con JavaScript, mi sono trovato a realizzare qualcosa che si avvicina di molto ad un wizard, ovvero un'interazione sequenziale con l'utente che abbia anche la caratteristica della reversibilità, ovvero fornisca all'utente la possibilità di tornare sui propri passi.

Per fare ciò, ho sfruttato l'estrema dinamicità del linguaggio JavaScript, ed ho semplicemente creato una lista di oggetti simile alla seguente:


MYAPP.phases = [
{
'title' : 'breve descrizione fase 1',
'desc' : 'lunga descrizione fase 1',
'handler' : function() {
// Lista di istruzioni da eseguire
// non appena si passa alla fase 1
},
'event1_handler' : function() {
// Lista di istruzioni da eseguire
// se si verifica l'evento 1 nella
// fase 1
}
},
{
'title' : 'breve descrizione fase 2',
'desc' : 'lunga descrizione fase 2',
'handler' : function() {
// Lista di istruzioni da eseguire
// non appena si passa alla fase 2
},
'event1_handler' : function() {
// Lista di istruzioni da eseguire
// se si verifica l'evento 1 nella
// fase 2
}
},
[altri N - 2 elementi di questo tipo...]
};


Ciascuno di questi oggetti implementa un'interfaccia comune, che mi permetterà di scrivere codice indipendente dallo stato in cui mi trovo, secondo la ben nota tecnica del pattern State.

La velocità del linguaggio JavaScript permette di implementare questo pattern senza la necessità di creare una gerarchia di classi, in maniera molto veloce ed intuitiva. Creo direttamente gli oggetti, non ho bisogno di creare una classe!
Il codice risulta compatto e di facile comprensione per chi abbia un minimo di confidenza con il linguaggio.

Per terminare l'implementazione, è necessario mantenere traccia dello stato corrente, creare una funzione di variazione di stato e creare due funzioni che si occupino di incrementare o decrementare lo stato attuale:

MYAPP.current_phase = 0;

MYAPP.setPhase = function(num) {
var cur = MYAPP.phases[num];
$("#phase-title").empty();
$("#phase-title").append("Fase " + (num + 1) + ": " + cur.title).wrap("");

$("#phase-desc").empty();
$("#phase-desc").append(cur.desc);

cur.handler();
MYAPP.current_phase = num;
}

MYAPP.next_phase = function() {
if(MYAPP.current_phase < MYAPP.phases.length - 1)
MYAPP.setPhase(++MYAPP.current_phase);
}

MYAPP.prev_phase = function() {
if(MYAPP.current_phase > 0)
MYAPP.setPhase(--MYAPP.current_phase);
}

Nella funzione di variazione di stato ho lasciato parte del codice che ho utilizzato nella mia applicazione, che utilizza l'ottimo framework jQuery.

Quel codice cambia il contenuto dei div #phase-title e #phase-desc a seconda dello stato attuale, richiama l'handler dello stato attuale e modifica il valore della variabile che mantiene traccia dello stato.

Per far sì che il comportamento dell'applicazione dipenda dallo stato è necessario utilizzare la tecnica del doppio inoltro, ad esempio in questo modo:

<a onclick = 'MYAPP.phases[MYAPP.current_phase].event1_handler();'>Testo</a>

Così l'azione che si verifica al click dipenderà dallo stato corrente, e non sarà necessario scrivere codice pieno di istruzioni switch che vanno modificate di volta in volta.

Considerando che all'utente è consentito scorrazzare abbastanza liberamente tra gli stati, sarà necessario implementare meccanismi robusti di gestione dell'errore ed adeguate politiche di reversibilità. Ricordate che si dovrebbe consentire all'utente di effettuare solo azioni lecite, in modo da incoraggiarlo ad utilizzare la vostra applicazione.

In conclusione, la dinamicità di JavaScript consente di implementare in maniera agile buona parte delle tecniche tradizionali di programmazione e di interazione, basta conoscere un po' il linguaggio ed utilizzare bene il paradigma che offre.

2 commenti:

maelstrom ha detto...

Mi sa che lo studio dei pattern sia stata una delle (poche) cose buone dei corsi di ingegneria del software... ormai penso sempre al Detonator!! :-)

lupino3 ha detto...

Eh sì, conoscere i pattern aiuta sia a capire quando utilizzarli sia - soprattutto - a capire quando NON utilizzarli :)

E poi, al limite, via di Commando! :)