Sealed Java State Machines

kilka lat temu napisałem o tym, jak zaimplementować maszyny stanowe, które zezwalają tylko na poprawne przejścia w czasie kompilacji w Javie.

to używało interfejsów zamiast enum, co miało dużą wadę—nie można było zagwarantować, że znasz wszystkie zaangażowane Stany. Ktoś może dodać inny stan w innym miejscu w bazie kodu, implementując interfejs.

Java 15 przynosi funkcję podglądu zamkniętych klas. Zamknięte klasy pozwalają nam rozwiązać ten problem. Teraz nasze maszyny stanowe oparte na interfejsie mogą nie tylko zapobiegać nieprawidłowym przejściom, ale także być wyliczane jak wyliczenia.

jeśli używasz jdk 15 z włączonymi funkcjami podglądu, możesz wypróbować kod. Tak wygląda definiowanie maszyny stanowej z interfejsami.

szczelny interfejs TrafficLight rozszerza Stan<TrafficLight> pozwala na Green, SolidAmber, FlashingAmber, Red {}static końcowa Klasa Green implementuje TrafficLight, TransitionTo<SolidAmber> {}static końcowa Klasa SolidAmber implementuje TrafficLight, TransitionTo<Red> {}static końcowa Klasa Red implementuje TrafficLight, TransitionTo<FlashingAmber> {}static końcowa Klasa Red implementuje TrafficLight, TransitionTo<FlashingAmber > {} static final class Flashingamber implementuje trafficlight, Transitionto < Green> {}

nowa część jest “zapieczętowana” i “pozwala”. Teraz staje się kompilacji błąd, aby zdefiniować nową implementację TrafficLight

, jak również istniejące zachowanie, gdzie jest to kompilacji błąd czasu, aby wykonać przejście, że sygnalizacja świetlna nie pozwalają.

N. b.możesz również pominąć czas kompilacji sprawdzanej wersji i nadal używać definicji typu do sprawdzania przejść w trybie runtime.

możliwe są również wielokrotne przejścia ze stanu

static final class Pending implementuje OrderStatus, BiTransitionTo< CheckingOut, anulowane> {}

dzięki szczelnym klasom możemy teraz również wykonywać wyliczenia w stylu enum i wyszukiwać na naszych maszynach stanowych opartych na interfejsie.

zamknięty interfejs OrderStatus rozszerza Stan<OrderStatus> zezwala na oczekujące, czekające, zakupione, wysłane, anulowane, nieudane, zwrócone {} @Test public void enumerable() { assertArrayEquals( array(Pending.Klasa, wymeldowanie.klasy, zakupione.Klasa, wysłane.zajęcia odwołane.Klasa, nieudana.Klasa, refundowane.Klasa), stan.wartości (OrderStatus.class)); assertEquals (0, new Pending().ordinal ()); assertEquals (3, new Shipped().ordinal ()); assertEquals(Purchased.Klasa, stan.valueOf (OrderStatus.class," Purchased"); assertEquals(anulowane.Klasa, stan.valueOf (OrderStatus.klasy "odwołane"));}

są to możliwe, ponieważ JEP 360 dostarcza API refleksyjne, za pomocą którego można wyliczyć dozwolone podklasy interfejsu. (Uwaga boczna JEP mówi getPermittedSubclasses (), ale implementacja wydaje się używać permittedSubclasses ())
możemy dodać use this, aby dodać powyższe metody wygody do naszego interfejsu stanu, aby umożliwić wyszukiwanie wartości (), ordinal () i valueOf ().

static < t extends State< T>>List< Class>valuesList(Class< T> stateMachineType) { assertSealed(stateMachineType); return Stream.of(stateMachineType.permittedSubclasses ()).map (Stan:: classFromDesc) .collect (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);}

jest więcej szczegółów na temat tego, jak działa sprawdzanie przejścia i więcej przykładów, gdzie może to być przydatne w oryginalnym poście. Kod jest na GitHubie.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.