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

新手上路,请多包涵

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

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

 private State mState;

public enum State {

    SOME_STATE {

        init() {
         ...
        }

        process() {
         ...
        }

    },

    ANOTHER_STATE {

        init() {
         ...
        }

        process() {
         ...
        }

    }

}

...

在我的情况下,一些状态会触发在特定对象上完成的一项工作,注册一个侦听器。该对象在工作完成后异步回调。也就是说,只是一个简单的回调接口。

同样,我有一个 EventBus。希望收到事件通知的类再次实现回调接口和 listen() 用于 EventBus 上的那些事件类型。

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

我使用的一种方法是对整个 enum 实现回调接口。枚举本身在底部有回调方法的默认实现,然后各个状态可以为他们感兴趣的事件覆盖这些回调方法。为此,每个状态必须在进入和退出时注册和取消注册,否则存在回调发生在非当前状态的风险。如果我找不到更好的东西,我可能会坚持下去。

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

 enum Events {
    SOMETHING_HAPPENED,
    ...
}

...

onSometingHappened() {

    mState.process( SOMETHING_HAPPENED );
}

但是我不喜欢这个,因为(a)我有丑陋的需要 switch 在每个状态的 process(event) 中的事件类型,和(b)通过额外的参数看起来很尴尬。

我想要一个优雅的解决方案的建议,而无需诉诸于使用图书馆。

原文由 Trevor 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 650
2 个回答

因此,您想将事件分派给当前状态的处理程序。

要分派到当前状态,在每个状态变为活动状态时订阅它,并在它变为非活动状态时取消订阅是相当麻烦的。订阅一个知道活动状态的对象并简单地将所有事件委托给活动状态会更容易。

要区分事件,您可以使用单独的事件对象,然后使用 访问者模式 来区分它们,但这是相当多的样板代码。只有当我有其他代码对所有事件都一视同仁时(例如,如果事件必须在传递前被缓冲),我才会这样做。否则,我只会做类似的事情

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

编辑

如果您有许多事件类型,最好在运行时使用字节码工程库甚至普通 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();
                }
            }
        });
    }
}

这降低了代码的可读性,但确实消除了为每种事件类型编写委托代码的需要。

原文由 meriton 发布,翻译遵循 CC BY-SA 4.0 许可协议

为什么不让事件直接调用正确的状态回调?

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

这解决了您对原始方法的两个保留:没有“丑陋”的开关,也没有“笨拙”的附加参数。

原文由 Dima 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题