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

2022-09-01 07:25:31

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

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

private State mState;

public enum State {

    SOME_STATE {


        init() {
         ... 
        }


        process() {
         ... 
        }


    },


    ANOTHER_STATE {

        init() {
         ... 
        }

        process() {
         ... 
        }

    }

}

...

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

同样,我有一个事件总线。希望再次收到事件通知的类再次实现回调接口,并针对 EventBus 上的这些事件类型实现回调接口。listen()

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

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

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

enum Events {
    SOMETHING_HAPPENED,
    ...
}

...

onSometingHappened() {

    mState.process( SOMETHING_HAPPENED );
}

然而,我不喜欢这样,因为(a)我需要在每个状态的事件类型上,并且(b)传递其他参数看起来很尴尬。switchprocess(event)

我想要一个优雅的解决方案的建议,而不诉诸于使用库。


答案 1

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

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

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


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

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