我在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 回答
您可能想尝试使用Command pattern:Command接口对应于您的类似"SOMETHING_HAPPENED" . 然后,每个枚举值都使用特定命令进行实例化,该命令可以通过Reflection实例化,并且可以运行execute方法(在Command接口中定义) .
如果有用,请考虑State pattern .
如果命令很复杂,请考虑Composite pattern .
因此,您希望将事件分派给其处理程序以获取当前状态 .
要调度到当前状态,在每个状态变为活动状态时对其进行预订,并在其变为非活动状态时取消订阅是相当麻烦的 . 订阅知道活动状态的对象更容易,并且只是将所有事件委托给活动状态 .
要区分事件,您可以使用单独的事件对象,然后使用visitor pattern区分它们,但是如果我有其他代码将所有事件都视为相同(例如,如果事件必须在传递之前缓冲),那么's quite a bit of boilerplate code. I' d只会执行此操作 . 否则,我会做一些类似的事情
Edit
如果您有许多事件类型,最好使用字节码工程库或甚至普通的JDK代理在运行时生成无聊的委托代码:
这使得代码的可读性降低,但无需为每种事件类型编写委托代码 .
为什么没有事件直接在状态上调用正确的回调?
这解决了原始方法的两种保留:没有“丑陋”的开关,也没有“尴尬”的附加参数 .
你处于良好的轨道上,你应该使用Strategy pattern与状态机结合使用 . 在状态枚举中实现事件处理,提供默认的通用实现,并可能添加特定的实现 .
定义您的事件和相关的策略界面:
然后,在
State
枚举中:根据
EventStrategy
,所有事件都有一个默认实现 . 此外,对于每个状态,可以针对不同的事件处理进行特定实现 .StateMachine
看起来像那样:在这种情况下,您信任mState是当前活动状态,所有事件仅应用于此状态 . 如果你想添加一个安全层,要禁用所有非活动状态的所有事件,你可以这样做,但在我看来,'s not a good pattern, it'不能达到
State
来知道它是否是's active but it' sStateMachine
job .当你已经拥有一个事件总线时,我不清楚为什么你需要一个回调接口 . 总线应该能够根据事件类型向侦听器传递事件,而无需接口 . 考虑像Guava's这样的架构(我知道你想要引起你注意的设计) .
我相信这种方法与你对梅里顿的答案的第一个评论一致:
如何使用访问者实现事件处理:
在我看来,状态图定义和事件处理代码看起来很小 . :)而且,通过更多的工作,它可以使用通用输入类型 .
Java 8的替代方案可能是使用具有默认方法的接口,如下所示: