Sealed Java State Machines

een paar jaar geleden heb ik gepost over het implementeren van state machines die alleen geldige overgangen toestaan tijdens het compileren in Java.

dit gebruikte interfaces in plaats van enums, wat een groot nadeel had—je kon niet garanderen dat je alle betrokken staten kent. Iemand kan een andere staat elders in je codebase toevoegen door de interface te implementeren.

Java 15 biedt een voorbeeldfunctie voor verzegelde klassen. Verzegelde klassen stellen ons in staat om dit nadeel op te lossen. Nu kunnen onze interface – gebaseerde staatsmachines niet alleen ongeldige overgangen voorkomen, maar ook opgesomd worden zoals enums.

als u jdk 15 gebruikt met voorbeeldfuncties ingeschakeld, kunt u de code uitproberen. Dit is hoe het eruit ziet om een staat machine met interfaces te definiëren.

verzegeld interface Stoplicht breidt Staat<Stoplicht> vergunningen Groen, SolidAmber, FlashingAmber, Rood {}static final class Groene implementeert Stoplicht, TransitionTo<SolidAmber> {}static final class SolidAmber implementeert Stoplicht, TransitionTo<Rood> {}static final class Rode implementeert Stoplicht, TransitionTo<FlashingAmber> {}static final class FlashingAmber implementeert Stoplicht, TransitionTo<Groen> {}

Het nieuwe deel is “afgesloten” en “vergunningen”. Nu wordt het een compilatiefout om een nieuwe implementatie van TrafficLight

te definiëren, evenals het bestaande gedrag waarbij het een compilatietijdfout is om een overgang uit te voeren die verkeerslichten niet toestaan.

n. b. U kunt ook de versie met compilatietijd overslaan en nog steeds de typedefinities gebruiken om de overgangen te controleren.

meerdere overgangen zijn ook mogelijk vanuit een toestand

static final class Pending implementeert OrderStatus, Bitransitie naar <afrekenen, geannuleerd> {}

dankzij sealed classes kunnen we nu ook enum style enumeration en lookups doen op onze interface gebaseerde state machines.

sealed interface OrderStatus breidt Status<OrderStatus> vergunningen in behandeling, afrekenen, gekocht, verzonden, geannuleerd, mislukt, terugbetaald {} @Test public void enumerable() { assertArrayEquals( array(in behandeling.klas, uitchecken.klasse, gekocht.klas, verscheept.klas, afgelast.klas, mislukt.klas, terugbetaald.klasse), staat.waarden (OrderStatus.class)); assertEquals (0, new Pending ().ordinal ()); assertEquals(3, new Shipped ().ordinal ()); assertEquals (gekocht.klasse, staat.waarde van (OrderStatus.klasse, "gekocht")); assertEquals(geannuleerd.klasse, staat.waarde van (OrderStatus.klasse, "geannuleerd"));}

deze zijn mogelijk omdat JEP 360 een reflectie API biedt waarmee men de toegestane subklassen van een interface kan opsommen. (kant opmerking het JEP zegt getPermittedSubclasses() maar de implementatie lijkt permittedSubclasses() )
te gebruiken we kunnen dit toevoegen om de bovenstaande convenience methoden toe te voegen aan onze State interface om de waarden(), ordinal(), en value of() lookups toe te staan.

static <T breidt Status<T>> List<Class> valuesList(Class<T> stateMachineType) {assertSealed(stateMachineType); return Stream.van (stateMachineType.toegelaten subklassen ()).kaart (staat:: classFromDesc).verzamelen (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);}

er zijn meer details over hoe de overgang controle werkt en meer voorbeelden van waar dit nuttig zou kunnen zijn in de oorspronkelijke post. De Code is op github.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.