In UML Distilled di Martin Fowler , afferma (nessun gioco di parole) nel Capitolo 10 Diagrammi della macchina a stati (enfasi mia):
Un diagramma di stato può essere implementato in tre modi principali: switch annidato , modello di stato e
tabelle di stato .
Usiamo un esempio semplificato degli stati del display di un telefono cellulare:
Interruttore annidato
Fowler ha fornito un esempio di codice C #, ma l'ho adattato al mio esempio.
public void HandleEvent(PhoneEvent anEvent) {
switch (CurrentState) {
case PhoneState.ScreenOff:
switch (anEvent) {
case PhoneEvent.PressButton:
if (powerLow) { // guard condition
DisplayLowPowerMessage(); // action
// CurrentState = PhoneState.ScreenOff;
} else {
CurrentState = PhoneState.ScreenOn;
}
break;
case PhoneEvent.PlugPower:
CurrentState = PhoneState.ScreenCharging;
break;
}
break;
case PhoneState.ScreenOn:
switch (anEvent) {
case PhoneEvent.PressButton:
CurrentState = PhoneState.ScreenOff;
break;
case PhoneEvent.PlugPower:
CurrentState = PhoneState.ScreenCharging;
break;
}
break;
case PhoneState.ScreenCharging:
switch (anEvent) {
case PhoneEvent.UnplugPower:
CurrentState = PhoneState.ScreenOff;
break;
}
break;
}
}
Modello di stato
Ecco un'implementazione del mio esempio con il pattern GoF State:
Tabelle di stato
Prendendo ispirazione da Fowler, ecco una tabella per il mio esempio:
Stato di origine Stato di destinazione Azione di protezione eventi
-------------------------------------------------- ------------------------------------
Schermo Spento Schermo Spento premere Pulsante alimentazione Bassa visualizzazione Bassa potenza Messaggio
Schermo Fuori dallo schermo Su premere il pulsante! PowerLow
ScreenOn ScreenOff premere il pulsante
Schermo Fuori schermo Spina di ricarica Alimentazione
ScreenOn Screen Connettore di ricarica Alimentazione
ScreenCharging ScreenOff unplugPower
Confronto
L'interruttore annidato mantiene tutta la logica in un punto, ma il codice può essere difficile da leggere quando ci sono molti stati e transizioni. È forse più sicuro e più facile da convalidare rispetto agli altri approcci (nessun polimorfismo o interpretazione).
L'implementazione del pattern State potenzialmente diffonde la logica su diverse classi separate, il che può rendere la sua comprensione nel suo insieme un problema. D'altra parte, le classi piccole sono facili da capire separatamente. Il design è particolarmente fragile se si modifica il comportamento aggiungendo o rimuovendo le transizioni, poiché sono metodi nella gerarchia e potrebbero esserci molte modifiche al codice. Se vivi secondo il principio di progettazione delle piccole interfacce, vedrai che questo modello non funziona molto bene. Tuttavia, se la macchina a stati è stabile, tali modifiche non saranno necessarie.
L'approccio delle tabelle di stato richiede la scrittura di un qualche tipo di interprete per il contenuto (questo potrebbe essere più facile se hai una riflessione nella lingua che stai usando), che potrebbe essere molto lavoro da fare in anticipo. Come sottolinea Fowler, se la tua tabella è separata dal tuo codice, potresti modificare il comportamento del tuo software senza ricompilarlo. Tuttavia, ciò ha alcune implicazioni sulla sicurezza; il software si comporta in base al contenuto di un file esterno.
Modifica (non proprio per il linguaggio C)
Esiste anche un approccio all'interfaccia fluente (noto anche come linguaggio interno specifico del dominio), probabilmente facilitato dai linguaggi che hanno funzioni di prima classe . La libreria Stateless esiste e quel blog mostra un semplice esempio con il codice. Viene discussa un'implementazione Java (pre Java8) . Mi è stato mostrato anche un esempio di Python su GitHub .