valueof type example enum define data java enums state-machines fsm finite-state-machine

type - java enum string



Máquina de estado basada en enum de Java(FSM): Pasar eventos (7)

¿Por qué no hacer que los eventos llamen directamente a la devolución de llamada correcta en estado?

public enum State { abstract State processFoo(); abstract State processBar(); State processBat() { return this; } // A default implementation, so that states that do not use this event do not have to implement it anyway. ... State1 { State processFoo() { return State2; } ... }, State2 { State processFoo() { return State1; } ... } } public enum Event { abstract State dispatch(State state); Foo { State dispatch(State s) { return s.processFoo(); } }, Bar { State dispatch(State s) { return s.processBar(); } } ... }

Esto aborda ambas reservas con el enfoque original: sin interruptor "feo" y sin parámetros adicionales "incómodos".

Estoy usando varias máquinas de estado basadas en enum en mi aplicación Android. Si bien funcionan muy bien, lo que estoy buscando es una sugerencia de cómo recibir elegantemente eventos, normalmente de devoluciones de llamadas registradas o mensajes de Eventbus, en el estado actualmente activo. De los muchos blogs y tutoriales sobre FSM basados ​​en enum, la mayoría de ellos dan ejemplos de máquinas de estado que consumen datos (por ejemplo, analizadores sintácticos) en lugar de mostrar cómo estas FSM pueden ser impulsadas por eventos.

Una máquina de estado típica que estoy usando tiene esta forma:

private State mState; public enum State { SOME_STATE { init() { ... } process() { ... } }, ANOTHER_STATE { init() { ... } process() { ... } } } ...

En mi situación, algunos estados desencadenan un trabajo en un objeto en particular, registrando un oyente. Ese objeto llama de manera asincrónica cuando el trabajo está hecho. En otras palabras, solo una simple interfaz de devolución de llamada.

Del mismo modo, tengo un EventBus. Las clases que desean recibir notificaciones de eventos implementan una interfaz de devolución de llamada y listen() para esos tipos de eventos en EventBus.

El problema básico, por lo tanto, es que la máquina de estado, o sus estados individuales, o la clase que contiene el FSM enum, o algo así, tiene que implementar esas interfaces de devolución de llamada, de modo que puedan representar eventos en el estado actual.

Un enfoque que he usado es para toda la enum para implementar la (s) interfaz (es) de devolución de llamada. La enumeración tiene implementaciones predeterminadas de los métodos de devolución de llamada en la parte inferior, y los estados individuales pueden anular esos métodos de devolución de llamada para los eventos en los que están interesados. Para que esto funcione, cada estado debe registrarse y anular el registro cuando ingresa y sale, de lo contrario existe el riesgo de que la devolución de llamada ocurra en un estado que no es el estado actual. Probablemente me quedaré con esto si no encuentro nada mejor.

Otra forma es que la clase contenedora implemente las devoluciones de llamada. Luego debe delegar esos eventos en la máquina de estado, llamando a mState.process( event ) . Eso significa que necesitaría enumerar tipos de eventos. Por ejemplo:

enum Events { SOMETHING_HAPPENED, ... } ... onSometingHappened() { mState.process( SOMETHING_HAPPENED ); }

Sin embargo, esto no me gusta porque (a) tendría la fealdad de tener que switch los tipos de eventos dentro del process(event) de cada estado, y (b) pasar por parámetros adicionales parece incómodo.

Me gustaría una sugerencia para una solución elegante para esto sin recurrir al uso de una biblioteca.


¿Qué hay de la implementación de manejo de eventos con los visitantes:

import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; public class StateMachine { interface Visitor { void visited(State state); } enum State { // a to A, b to B A(''a'',"A",''b'',"B"), // b to B, b is an end-state B(''b'',"B") { @Override public boolean endState() { return true; } }, ; private final Map<Character,String> transitions = new LinkedHashMap<>(); private State(Object...transitions) { for(int i=0;i<transitions.length;i+=2) this.transitions.put((Character) transitions[i], (String) transitions[i+1]); } private State transition(char c) { if(!transitions.containsKey(c)) throw new IllegalStateException("no transition from "+this+" for "+c); return State.valueOf(transitions.get(c)).visit(); } private State visit() { for(Visitor visitor : visitors) visitor.visited(this); return this; } public boolean endState() { return false; } private final List<Visitor> visitors = new LinkedList<>(); public final void addVisitor(Visitor visitor) { visitors.add(visitor); } public State process(String input) { State state = this; for(char c : input.toCharArray()) state = state.transition(c); return state; } } public static void main(String args[]) { String input = "aabbbb"; Visitor commonVisitor = new Visitor() { @Override public void visited(State state) { System.out.println("visited "+state); } }; State.A.addVisitor(commonVisitor); State.B.addVisitor(commonVisitor); State state = State.A.process(input); System.out.println("endState = "+state.endState()); } }

La definición del diagrama de estado y el código de manejo de eventos parecen bastante mínimos en mi opinión. :) Y, con un poco más de trabajo, se puede hacer que funcione con un tipo de entrada genérico.


Estás en buenas pistas, debes usar un patrón de Estrategia combinado con tu máquina de estado. Implemente el manejo de eventos en su enumeración de estado, proporcionando una implementación común predeterminada y posiblemente agregue implementaciones específicas.

Define tus eventos y la interfaz de estrategia asociada:

enum Event { EVENT_X, EVENT_Y, EVENT_Z; // Other events... } interface EventStrategy { public void onEventX(); public void onEventY(); public void onEventZ(); // Other events... }

Luego, en tu enum de State :

enum State implements EventStrategy { STATE_A { @Override public void onEventX() { System.out.println("[STATE_A] Specific implementation for event X"); } }, STATE_B { @Override public void onEventY() { System.out.println("[STATE_B] Default implementation for event Y"); } public void onEventZ() { System.out.println("[STATE_B] Default implementation for event Z"); } }; // Other states... public void process(Event e) { try { // Google Guava is used here Method listener = this.getClass().getMethod("on" + CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, e.name())); listener.invoke(this); } catch (Exception ex) { // Missing event handling or something went wrong throw new IllegalArgumentException("The event " + e.name() + " is not handled in the state machine", ex); } } // Default implementations public void onEventX() { System.out.println("Default implementation for event X"); } public void onEventY() { System.out.println("Default implementation for event Y"); } public void onEventZ() { System.out.println("Default implementation for event Z"); } }

Según EventStrategy , hay una implementación predeterminada para todos los eventos. Además, para cada estado, es posible una implementación específica, para un manejo de eventos diferente.

El StateMachine se vería así:

class StateMachine { // Active state State mState; // All the code about state change public void onEvent(Event e) { mState.process(e); } }

En este escenario, confía en que mState es el estado activo actual, todos los eventos se aplican solo en este estado. Si desea agregar una capa de seguridad, para deshabilitar todos los eventos para todos los estados no activos, puede hacerlo, pero en mi opinión, no es un buen patrón, no le corresponde a un State saber si está activo, pero es StateMachine trabajo de StateMachine .


No tengo claro por qué necesitas una interfaz de devolución de llamada cuando ya tienes un bus de eventos. El bus debería poder entregar eventos a los oyentes en función del tipo de evento sin la necesidad de interfaces. Considere una arquitectura como la de Guava (sé que no quiere recurrir a bibliotecas externas, es el diseño lo que quiero llamar su atención).

enum State { S1 { @Subscribe void on(EventX ex) { ... } }, S2 { @Subscribe void on(EventY ey) { ... } } } // when a state becomes active eventBus.register(currentState); eventBus.unregister(previousState);

Creo que este enfoque sigue las líneas de su primer comentario a la respuesta de Meriton:

En lugar de escribir manualmente la clase StateMachine para implementar las mismas interfaces y delegar eventos a currentState, podría ser posible automatizar esto mediante el reflejo (o algo así). Entonces la clase externa se registraría como un oyente para esas clases en tiempo de ejecución y las delegaría, y registraría / anularía el registro del estado cuando entre / salga.


Por lo tanto, desea enviar eventos a sus controladores para el estado actual.

Para enviar al estado actual, suscribirse cada estado cuando se activa, y darse de baja cuando se vuelve inactivo es bastante engorroso. Es más fácil suscribir un objeto que conoce el estado activo y simplemente delega todos los eventos al estado activo.

Para distinguir los eventos, puede usar objetos de evento por separado y luego distinguirlos con el patrón de visitante , pero eso es bastante un código repetitivo. Solo haría esto si tuviera otro código que trate a todos los eventos de la misma manera (por ejemplo, si los eventos deben almacenarse temporalmente antes de la entrega). De lo contrario, simplemente haría algo así como

interface StateEventListener { void onEventX(); void onEventY(int x, int y); void onEventA(String s); } enum State implements StateEventListener { initialState { @Override public void onEventX() { // do whatever } // same for other events }, // same for other states } class StateMachine implements StateEventListener { State currentState; @Override public void onEventX() { currentState.onEventX(); } @Override public void onEventY(int x, int y) { currentState.onEventY(x, y); } @Override public void onEventZ(String s) { currentState.onEventZ(s); } }

Editar

Si tiene muchos tipos de eventos, podría ser mejor generar el código de delegación aburrido en tiempo de ejecución utilizando una biblioteca de ingeniería de códigos de bytes, o incluso un proxy JDK simple:

class StateMachine2 { State currentState; final StateEventListener stateEventPublisher = buildStateEventForwarder(); StateEventListener buildStateEventForwarder() { Class<?>[] interfaces = {StateEventListener.class}; return (StateEventListener) Proxy.newProxyInstance(getClass().getClassLoader(), interfaces, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return method.invoke(currentState, args); } catch (InvocationTargetException e) { throw e.getCause(); } } }); } }

Esto hace que el código sea menos legible, pero elimina la necesidad de escribir código de delegación para cada tipo de evento.


Puede intentar usar el patrón de comando : la interfaz de comando corresponde a algo así como su "ALGO_HECHO". Cada valor enum, entonces, se instancia con un comando particular, que puede ser instanciado a través de Reflection y puede ejecutar el método de ejecución (definido en la interfaz de comando).

Si es útil, considere también el patrón de estado .

Si los comandos son complejos, considere también el patrón Compuesto .


Una alternativa para Java 8 podría ser utilizar una interfaz con métodos predeterminados, como este:

public interface IPositionFSM { default IPositionFSM processFoo() { return this; } default IPositionFSM processBar() { return this; } } public enum PositionFSM implements IPositionFSM { State1 { @Override public IPositionFSM processFoo() { return State2; } }, State2 { @Override public IPositionFSM processBar() { return State1; } }; }