首页 文章

基于Java枚举的状态机(FSM):传递事件

提问于
浏览
32

我在Android应用程序中使用了几个基于枚举的状态机 . 虽然这些工作非常好,但我正在寻找的是如何优雅地接收事件,通常是从已注册的回调或从事件总线消息接收到当前活动状态的建议 . 在有关基于枚举的FSM的许多博客和教程中,大多数都提供了使用数据(例如解析器)的状态机的示例,而不是展示如何从事件驱动这些FSM .

我正在使用的典型状态机具有以下形式:

private State mState;

public enum State {

    SOME_STATE {


        init() {
         ... 
        }


        process() {
         ... 
        }


    },


    ANOTHER_STATE {

        init() {
         ... 
        }

        process() {
         ... 
        }

    }

}

...

在我的情况下,一些状态会触发一项特定对象的工作,注册一个监听器 . 该工作完成后,该对象将异步回调 . 换句话说,只是一个简单的回调接口 .

同样,我有一个EventBus . 希望被事件通知的类再次为EventBus上的事件类型实现回调接口和 listen() .

因此,基本问题是状态机或其各个状态,或包含枚举FSM的类,或某些必须实现这些回调接口,以便它们可以表示当前状态的事件 .

我使用的一种方法是让整个 enum 实现回调接口 . 枚举本身在底部具有回调方法的默认实现,然后各个状态可以覆盖那些它们当前状态的事件的回调方法 . 如果我找不到更好的东西,我可能会坚持这一点 .

另一种方法是包含类来实现回调 . 然后,它必须通过调用 mState.process( event ) 将这些事件委托给状态机 . 这意味着我需要枚举事件类型 . 例如:

enum Events {
    SOMETHING_HAPPENED,
    ...
}

...

onSometingHappened() {

    mState.process( SOMETHING_HAPPENED );
}

我不需要't like this however because (a) I'对每个州的 process(event) 中的事件类型需要 switch ,并且(b)传递其他参数看起来很尴尬 .

我想建议一个优雅的解决方案,而无需使用库 .

7 回答

  • 16

    您可能想尝试使用Command pattern:Command接口对应于您的类似"SOMETHING_HAPPENED" . 然后,每个枚举值都使用特定命令进行实例化,该命令可以通过Reflection实例化,并且可以运行execute方法(在Command接口中定义) .

    如果有用,请考虑State pattern .

    如果命令很复杂,请考虑Composite pattern .

  • 3

    因此,您希望将事件分派给其处理程序以获取当前状态 .

    要调度到当前状态,在每个状态变为活动状态时对其进行预订,并在其变为非活动状态时取消订阅是相当麻烦的 . 订阅知道活动状态的对象更容易,并且只是将所有事件委托给活动状态 .

    要区分事件,您可以使用单独的事件对象,然后使用visitor pattern区分它们,但是如果我有其他代码将所有事件都视为相同(例如,如果事件必须在传递之前缓冲),那么's quite a bit of boilerplate code. I' d只会执行此操作 . 否则,我会做一些类似的事情

    interface StateEventListener {
        void onEventX();
        void onEventY(int x, int y);
        void onEventZ(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

    如果您有许多事件类型,最好使用字节码工程库或甚至普通的JDK代理在运行时生成无聊的委托代码:

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

    这使得代码的可读性降低,但无需为每种事件类型编写委托代码 .

  • 6

    为什么没有事件直接在状态上调用正确的回调?

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

    这解决了原始方法的两种保留:没有“丑陋”的开关,也没有“尴尬”的附加参数 .

  • 22

    你处于良好的轨道上,你应该使用Strategy pattern与状态机结合使用 . 在状态枚举中实现事件处理,提供默认的通用实现,并可能添加特定的实现 .

    定义您的事件和相关的策略界面:

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

    然后,在 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");
        }
    }
    

    根据 EventStrategy ,所有事件都有一个默认实现 . 此外,对于每个状态,可以针对不同的事件处理进行特定实现 .

    StateMachine 看起来像那样:

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

    在这种情况下,您信任mState是当前活动状态,所有事件仅应用于此状态 . 如果你想添加一个安全层,要禁用所有非活动状态的所有事件,你可以这样做,但在我看来,'s not a good pattern, it'不能达到 State 来知道它是否是's active but it' s StateMachine job .

  • 3

    当你已经拥有一个事件总线时,我不清楚为什么你需要一个回调接口 . 总线应该能够根据事件类型向侦听器传递事件,而无需接口 . 考虑像Guava's这样的架构(我知道你想要引起你注意的设计) .

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

    我相信这种方法与你对梅里顿的答案的第一个评论一致:

    不是手动编写类StateMachine来实现相同的接口并将事件转发到currentState,而是可以使用反射(或其他东西)自动化它 . 然后外部类将在运行时注册为这些类的侦听器并将它们委托给它们,并在进入/退出时注册/取消注册状态 .

  • 5

    如何使用访问者实现事件处理:

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

    在我看来,状态图定义和事件处理代码看起来很小 . :)而且,通过更多的工作,它可以使用通用输入类型 .

  • 3

    Java 8的替代方案可能是使用具有默认方法的接口,如下所示:

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

相关问题