Förseglade Java State Machines

för några år tillbaka skrev jag om hur man implementerar statliga maskiner som endast tillåter giltiga övergångar vid kompileringstid i Java.

detta använde gränssnitt istället för enums, vilket hade en stor nackdel—du kunde inte garantera att du känner till alla berörda stater. Någon kan lägga till ett annat tillstånd någon annanstans i din kodbas genom att implementera gränssnittet.

Java 15 ger en förhandsgranskningsfunktion av förseglade klasser. Förseglade klasser gör det möjligt för oss att lösa denna nackdel. Nu kan våra gränssnittsbaserade statliga maskiner inte bara förhindra ogiltiga övergångar utan också vara uppräknade som enums.

om du använder jdk 15 med förhandsgranskningsfunktioner aktiverade kan du prova koden. Så här ser det ut att definiera en tillståndsmaskin med gränssnitt.

förseglat gränssnitt TrafficLight förlänger tillståndet<TrafficLight> tillstånd grön, SolidAmber, FlashingAmber, röd {}statisk slutklass grön implementerar TrafficLight, TransitionTo<SolidAmber> {}statisk slutklass SolidAmber implementerar TrafficLight, TransitionTo<röd> {}statisk slutklass röd implementerar TrafficLight, TransitionTo<FlashingAmber> {}statisk slutklass Flashingamber implementerar TrafficLight, Transitionto<grön> {}

den nya delen är” förseglad “och”tillstånd”. Nu blir det ett kompileringsfel att definiera en ny implementering av TrafficLight

samt det befintliga beteendet där det är ett kompileringstidsfel att utföra en övergång som trafikljus inte tillåter.

n.b. Du kan också hoppa över kompileringstiden kontrollerad version och fortfarande använda typdefinitioner för att runtime kontrollera övergångarna.

flera övergångar är också möjliga från ett tillstånd

statisk slutklass väntande redskap OrderStatus, BiTransitionTo<CheckingOut, avbruten> {}

tack vare förseglade klasser kan vi nu också göra enum stil uppräkning och uppslag på våra gränssnitt baserade statliga maskiner.

förseglat gränssnitt OrderStatus förlänger tillståndet<OrderStatus > tillstånd väntar, CheckingOut, köpt, levereras, annulleras, misslyckades, återbetalas {} @Test public void enumerable() { assertArrayEquals( array(väntar.klass, CheckingOut.klass, köpt.klass, levereras.klass, inställd.klass, misslyckades.klass, återbetalas.klass), stat.värden (OrderStatus.klass)); assertEquals(0, nya väntande ().ordinär ()); assertEquals (3, nya levereras ().ordinär ()); assertEquals (köpt.klass, stat.valueOf (OrderStatus.klass, "köpt")); assertEquals(avbruten.klass, stat.valueOf (OrderStatus.klass, "avbruten"));}

dessa är möjliga eftersom JEP 360 tillhandahåller ett reflektions-API med vilket man kan räkna upp de tillåtna underklasserna i ett gränssnitt. (sidnot JEP säger getPermittedSubclasses () men implementeringen verkar använda permittedSubclasses ())
vi kan lägga till Använd detta för att lägga till ovanstående bekvämlighetsmetoder i vårt Tillståndsgränssnitt för att tillåta värdena (), ordinal() och valueOf () sökningar.

statisk < t förlänger tillståndet<T>> lista< klass> valuesList(klass<t> stateMachineType) { assertSealed (stateMachineType); returström.av (stateMachineType.permittedSubclasses ()).karta (stat::classFromDesc) .samla(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);}

det finns mer information om hur övergången kontroll fungerar och fler exempel på var detta kan vara användbart i det ursprungliga inlägget. Koden är på github.

Lämna ett svar

Din e-postadress kommer inte publiceras.