based state machine (FSM): Passing in events

I'm using several enum-based state machines in my Android application. While these work very well, what I am looking for is a suggestion for how to elegantly receive events, typically from registered callbacks or from eventbus messages, into the currently active state. Of the many blogs and tutorials concerning enum-based FSMs, most of them give examples of state machines that consume data (eg parsers) rather than show how these FSMs may be driven from events.

A typical state machine I'm using has this form:

private State mState;

public enum State {

    SOME_STATE {


        init() {
         ... 
        }


        process() {
         ... 
        }


    },


    ANOTHER_STATE {

        init() {
         ... 
        }

        process() {
         ... 
        }

    }

}

...

In my situation, some of the states trigger a piece of work to be done on a particular object, registering a listener. That object asynchronously calls back when the work is done. In other words, just a simple callback interface.

Similarly, I have an EventBus. Classes wanting to be notified of events again implement a callback interface and listen() for those event types on the EventBus.

The basic problem therefore is that the state machine, or its individual states, or the class containing the enum FSM, or something has to implement those callback interfaces, so that they can represent events on the current state.

One approach I have used is for the entire enum to implement the callback interface(s). The enum itself has default implementations of the callback methods at the bottom, and the individual states can then override those callback methods for events they're interested in. For this to work, each state must register and unregister as it enters and exits, otherwise there is risk of the callback happening on a state that isn't the current state. I will probably stick with this if I find nothing better.

Another way is for the containing class to implement the callbacks. It then has to delegate those events on to the state machine, by calling mState.process( event ) . That means I'd need to enumerate event types. For example:

enum Events {
    SOMETHING_HAPPENED,
    ...
}

...

onSometingHappened() {

    mState.process( SOMETHING_HAPPENED );
}

I don't like this however because (a) I'd have the uglyness of needing to switch on the event types within the process(event) of each state, and (b) passing through additional parameters looks awkward.

I would like a suggestion for an elegant solution for this without resorting to using a library.


So you want to dispatch events to their handlers for the current state.

To dispatch to the current state, subscribing each state as it becomes active, and unsubscribing it as it becomes inactive is rather cumbersome. It is easier to subscribe an object that knows the active state, and simply delegates all events to the active state.

To distinguish events, you can use separate event objects, and then distinguish them with the visitor pattern, but that's quite a bit of boilerplate code. I'd only do this if I have other code that treats all events the same (for instance, if events must be buffered before delivery). Otherwise, I'd simply do something like

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);
    }
}

Edit

If you have many event types, it might be better to generate the boring delegation code at runtime using a bytecode engineering library, or even a plain JDK proxy:

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();
                }
            }
        });
    }
}

This makes the code less readable, but does eliminate the need to write delegation code for each event type.


Why not have events call the right callback on state directly?

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(); }
   }
   ...
}

This addresses both of your reservations with the original approach: no "ugly" switch, and no "awkward" additional parameters.


You are on good tracks, you should use a Strategy pattern combined with your state machine. Implement event handling in your state enum, providing a default common implementation and possibly add specific implementations.

Define your events and the associated strategy interface :

enum Event
{
    EVENT_X,
    EVENT_Y,
    EVENT_Z;
    // Other events...
}

interface EventStrategy
{
    public void onEventX();
    public void onEventY();
    public void onEventZ();
    // Other events...
}

Then, in your State enum :

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");
    }
}

According to EventStrategy , there is a default implementation for all events. Moreover, for each state, a specific implementation, for a different event handling, is possible.

The StateMachine would look like that :

class StateMachine
{
    // Active state
    State mState;

    // All the code about state change

    public void onEvent(Event e)
    {
        mState.process(e);
    }
}

In this scenario, you trust mState being the current active state, all events are applied on this state only. If you want to add a security layer, to disable all events for all non active states, you can do it but in my opinion, it's not a good pattern, it's not up to a State to know if it's active but it's StateMachine job.

链接地址: http://www.djcxy.com/p/53204.html

上一篇: C ++ 1z协程线程上下文和协程调度

下一篇: 基于状态机(FSM):传入事件