Máquinas de estado Java selladas

Hace unos años publiqué sobre cómo implementar máquinas de estado que solo permiten transiciones válidas en tiempo de compilación en Java.

Esto usaba interfaces en lugar de enumeraciones, lo que tenía un gran inconveniente: no podía garantizar que conocía todos los estados involucrados. Alguien podría agregar otro estado en otro lugar de su base de código implementando la interfaz.

Java 15 trae una función de vista previa de clases selladas. Las clases selladas nos permiten resolver este inconveniente. Ahora, nuestras máquinas de estado basadas en interfaces no solo pueden evitar transiciones no válidas, sino que también pueden enumerarse como enumeraciones.

Si está utilizando jdk 15 con las funciones de vista previa habilitadas, puede probar el código. Así es como se ve definir una máquina de estados con interfaces.

sellado de la interfaz de TrafficLight se extiende Estado<TrafficLight> permisos Verde, SolidAmber, FlashingAmber, Rojo {}static final de la clase Verde implementa TrafficLight, TransitionTo<SolidAmber> {}static final de la clase SolidAmber implementa TrafficLight, TransitionTo<Rojo> {}static final de la clase de Red implementa TrafficLight, TransitionTo<FlashingAmber> {}static final de la clase FlashingAmber implementa TrafficLight, TransitionTo<Verde> {}

La parte nueva es “cerrado” y “permisos”. Ahora se convierte en un error de compilación definir una nueva implementación de TrafficLight

, así como el comportamiento existente en el que es un error de tiempo de compilación realizar una transición que los semáforos no permiten.

n.b. también puede omitir la versión comprobada en tiempo de compilación y seguir utilizando las definiciones de tipo para comprobar las transiciones en tiempo de ejecución.

También son posibles múltiples transiciones desde un estado

clase final estática de implementos pendientes Estado de pedido, transición a<Comprobación, Cancelado> {}

Gracias a las clases selladas, ahora también podemos hacer enumeraciones de estilo de enumeración y búsquedas en nuestras máquinas de estado basadas en interfaces.

OrderStatus de interfaz sellada extiende el Estado< OrderStatus> permite Pendiente, CheckingOut, Comprado, Enviado, Cancelado, Fallado, Reembolsado {} @Test public void enumerable () {assertArrayEquals(array (Pendiente.clase, chequeo.clase, Comprada.clase, Enviado.clase, Cancelada.clase, no.clase, Reembolsada.clase), Estado.valores(OrderStatus.class) ); assertEquals(0, new Pending ().ordinal ()); assertEquals (3, new Shipped().ordinal()); assertEquals(Comprado.clase, Estado.valueOf(OrderStatus.clase, "Comprado")); assertEquals(Cancelado.clase, Estado.valueOf(OrderStatus.clase, "Cancelada"));}

Esto es posible porque JEP 360 proporciona una API de reflexión con la que se pueden enumerar las subclases permitidas de una interfaz. (nota al margen el JEP dice getPermittedSubclasses () pero la implementación parece usar permittedSubclasses ())
Podemos agregar usar esto para agregar los métodos de conveniencia anteriores a nuestra interfaz de Estado para permitir las búsquedas de valores (), ordinal() y valueOf ().

static < T extiende el estado< T> > List< Class> valuesList(Class<T> stateMachineType) { assertSealed (stateMachineType); devuelve la secuencia.de (Tipo de máquina de estado.permittedSubclasses()) .mapa(Estado::classFromDesc) .recoger(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);}

Hay más detalles sobre cómo funciona la comprobación de transición y más ejemplos de dónde podría ser útil en la publicación original. El código está en github.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.