基于 Java 枚举的状态机 (FSM):传入事件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25581176/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java enum-based state machine (FSM): Passing in events
提问by Trevor
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 (e.g. parsers) rather than show how these FSMs may be driven from events.
我在我的 Android 应用程序中使用了几个基于枚举的状态机。虽然这些工作得很好,但我正在寻找关于如何优雅地接收事件的建议,通常来自注册的回调或事件总线消息,进入当前活动状态。在许多关于基于枚举的 FSM 的博客和教程中,大多数都给出了使用数据的状态机(例如解析器)的示例,而不是展示如何从事件驱动这些 FSM。
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.
同样,我有一个 EventBus。希望再次收到事件通知的类实现回调接口,并listen()
针对 EventBus 上的那些事件类型。
The basic problem therefore is that the state machine, or its individual states, or the class containing the enum FSM, or somethinghas to implement those callback interfaces, so that they can represent events on the current state.
因此,基本问题是状态机,或其各个状态,或包含枚举 FSM 的类,或某些东西必须实现这些回调接口,以便它们可以表示当前状态上的事件。
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.
我使用的一种方法是整体enum
实现回调接口。枚举本身在底部具有回调方法的默认实现,然后各个状态可以覆盖它们感兴趣的事件的回调方法。为此,每个状态必须在进入和退出时注册和注销,否则存在回调发生在非当前状态的风险。如果我发现没有更好的东西,我可能会坚持下去。
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:
另一种方法是让包含类实现回调。然后它必须通过调用将这些事件委托给状态机mState.process( event )
。这意味着我需要枚举事件类型。例如:
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.
然而,我不喜欢这样,因为 (a) 我需要在每个状态中switch
的事件类型上进行处理process(event)
,并且 (b) 传递附加参数看起来很尴尬。
I would like a suggestion for an elegant solution for this without resorting to using a library.
我想要一个优雅的解决方案的建议,而无需求助于使用库。
采纳答案by meriton
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 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
编辑
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:
如果您有许多事件类型,最好在运行时使用字节码工程库甚至是普通的 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();
}
}
});
}
}
This makes the code less readable, but does eliminate the need to write delegation code for each event type.
这降低了代码的可读性,但确实消除了为每种事件类型编写委托代码的需要。
回答by Manu
You may want to try using the Command pattern: the Command interface corresponds to something like your "SOMETHING_HAPPENED". Each enum value, then, is instantiated with a particular command, which may be instantiated via Reflection and can run the execute method (defined in the Command interface).
您可能想尝试使用命令模式:命令接口对应于您的“SOMETHING_HAPPENED”之类的东西。然后,每个枚举值都使用特定命令实例化,该命令可以通过反射实例化并且可以运行 execute 方法(在 Command 接口中定义)。
If useful, consider also the State pattern.
如果有用,还可以考虑State 模式。
If commands are complex, consider also the Composite pattern.
如果命令很复杂,还可以考虑复合模式。
回答by Dima
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.
这解决了您对原始方法的保留意见:没有“丑陋”的开关,也没有“尴尬”的附加参数。
回答by ToYonos
You are on good tracks, you should use a Strategy patterncombined 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 :
然后,在您的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");
}
}
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.
根据EventStrategy
,所有事件都有一个默认实现。此外,对于每个状态,针对不同事件处理的特定实现是可能的。
The StateMachine
would look like that :
该StateMachine
会看起来像:
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.
在这种情况下,您相信 mState 是当前活动状态,所有事件仅应用于此状态。如果你想添加一个安全层,禁用所有非活动状态的所有事件,你可以这样做,但在我看来,这不是一个好的模式,不State
知道它是否处于活动状态,但它的StateMachine
工作。
回答by Danny Daglas
How about implementing event handling with Visitors:
如何使用访客实现事件处理:
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());
}
}
The state-diagram definition and event-handling code look rather minimal in my opinion. :) And, with a little more work, it can be made to work with a generic input type.
在我看来,状态图定义和事件处理代码看起来相当少。:) 而且,通过更多的工作,它可以使用通用输入类型。
回答by ehecatl
It's not clear to me why you need a callback interface when you already have an event bus. The bus should be able to deliver events to listeners based on the event type without the need for interfaces. Consider an architecture like Guava's(I know you don't want to resort to external libraries, it's the design what I want to bring to your attention).
我不清楚为什么当您已经拥有事件总线时还需要回调接口。总线应该能够根据事件类型将事件传递给侦听器,而无需接口。考虑像Guava这样的架构(我知道你不想求助于外部库,这是我想要引起你注意的设计)。
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);
I believe this approach goes along the lines of your first comment to meriton's answer:
我相信这种方法符合您对梅里顿回答的第一条评论:
Instead of manually writing class StateMachine to implement the same interfaces and delegate events forward to currentState, it could be possible to automate this using reflection (or something). Then the outer class would register as a listener for those classes at runtime and delegate them on, and register/unregister the state as it enters/exits.
与其手动编写类 StateMachine 来实现相同的接口并将事件委托给 currentState,还可以使用反射(或其他东西)自动执行此操作。然后外部类将在运行时注册为这些类的侦听器并委托它们,并在进入/退出时注册/取消注册状态。
回答by vlp
An alternative for Java 8 might be to use an interface with default methods, like this:
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;
}
};
}
回答by Pravin
Simple example if you do not have events and just need next status public enum LeaveRequestState {
简单示例,如果您没有事件而只需要下一个状态 public enum LeaveRequestState {
Submitted {
@Override
public LeaveRequestState nextState() {
return Escalated;
}
@Override
public String responsiblePerson() {
return "Employee";
}
},
Escalated {
@Override
public LeaveRequestState nextState() {
return Approved;
}
@Override
public String responsiblePerson() {
return "Team Leader";
}
},
Approved {
@Override
public LeaveRequestState nextState() {
return this;
}
@Override
public String responsiblePerson() {
return "Department Manager";
}
};
public abstract LeaveRequestState nextState();
public abstract String responsiblePerson();
}