Macchine a stati Java sigillate

Alcuni anni fa ho pubblicato su come implementare macchine a stati che consentono solo transizioni valide in fase di compilazione in Java.

Questo utilizzava interfacce invece di enumerazioni, il che aveva un grosso inconveniente: non si poteva garantire di conoscere tutti gli stati coinvolti. Qualcuno potrebbe aggiungere un altro stato altrove nella tua base di codice implementando l’interfaccia.

Java 15 offre una funzionalità di anteprima delle classi sigillate. Le classi sigillate ci permettono di risolvere questo inconveniente. Ora le nostre macchine a stati basate sull’interfaccia non solo possono impedire transizioni non valide, ma anche essere enumerabili come enumerazioni.

Se stai usando jdk 15 con funzioni di anteprima abilitate puoi provare il codice. Ecco come appare definire una macchina a stati con interfacce.

sigillato interfaccia Semaforo si estende di Stato<Semaforo> permessi Verde, SolidAmber, FlashingAmber, Rosso {}static final classe Verde implementa Semaforo, TransitionTo<SolidAmber> {}static final classe SolidAmber implementa Semaforo, TransitionTo<Rosso> {}static final Rosso di classe che implementa Semaforo, TransitionTo<FlashingAmber> {}static final classe FlashingAmber implementa Semaforo, TransitionTo<Verde> {}

La parte nuova è “sigillato” e “permessi”. Ora diventa un errore di compilazione definire una nuova implementazione di TrafficLight

Così come il comportamento esistente in cui è un errore di compilazione eseguire una transizione che i semafori non consentono.

n.b. è anche possibile saltare la versione selezionata in fase di compilazione e utilizzare comunque le definizioni dei tipi per controllare le transizioni.

Sono possibili anche transizioni multiple da uno stato

classe finale statica In attesa implementa OrderStatus, BiTransitionTo < CheckingOut, Annullato> {}

Grazie alle classi sigillate possiamo anche ora fare enumerazione stile enumerazione e ricerche sulle nostre macchine a stati basati sull’interfaccia.

sealed interface OrderStatus estende Stato<OrderStatus> permessi in sospeso, CheckingOut, Acquistato, spedito, Annullato, fallito, rimborsato {} @ Test public void enumerable () { assertArrayEquals(array (In sospeso.lezione, uscita.classe, acquistato.classe, Spedito.lezione annullata.classe, Fallito.classe, rimborsato.classe), Stato.valori (OrderStatus.classe)); assertEquals (0, nuovo in sospeso ().ordinale ()); assertEquals(3, nuovo spedito ().ordinale ()); assertEquals (Acquistato.classe, Stato.valueOf (OrderStatus.classe, "Acquistato")); assertEquals (Annullato.classe, Stato.valueOf (OrderStatus.classe, "Annullato"));}

Questi sono possibili perché JEP 360 fornisce un’API di riflessione con cui è possibile enumerare le sottoclassi consentite di un’interfaccia. (nota a margine il JEP dice getPermittedSubclasses () ma l’implementazione sembra usare permittedSubclasses ())
Possiamo aggiungere use this per aggiungere i metodi di convenienza sopra alla nostra interfaccia di stato per consentire le ricerche values (), ordinal() e valueOf ().

static < T estende lo stato < T > > List < Classe > valuesList (Classe<T> stateMachineType) { assertSealed (stateMachineType); return Stream.di (stateMachineType.permittedSubclasses()) .mappa (Stato:: classFromDesc).raccogliere(ToList());} static <T extends State<T>> Class<T> valueOf(Class<T> stateMachineType, String name) { assertSealed(stateMachineType); return valuesList(stateMachineType) .stream() .filter(c -> Objects.equals(c.getSimpleName(), name)) .findFirst() .orElseThrow(IllegalArgumentException::new);}static <T extends State<T>, U extends T> int ordinal(Class<T> stateMachineType, Class<U> instanceType) { return valuesList(stateMachineType).indexOf(instanceType);}

Ci sono maggiori dettagli su come funziona il controllo della transizione e altri esempi di dove questo potrebbe essere utile nel post originale. Il codice è su github.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.