Akka Java FSM示例

问题描述 投票:10回答:2

请注意:我是一名Java开发人员,不熟悉Scala(遗憾的是)。我会问答案中提供的任何代码示例都将使用Akka的Java API。

我正在尝试使用Akka FSM API来建模以下超级简单的状态机。实际上,我的机器要复杂得多,但这个问题的答案将允许我推断我的实际FSM。

所以我有两个州:OffOn。您可以通过致电Off -> On来启动机器,从SomeObject#powerOn(<someArguments>)。您可以通过致电On -> Off来关闭机器,从SomeObject#powerOff(<someArguments>)

我想知道为了实现这个FSM,我需要哪些演员和支持类。我相信代表FSM的演员必须扩展AbstractFSM。但是什么类代表了两个州呢?什么代码暴露并实现了powerOn(...)powerOff(...)状态转换?一个有效的Java示例,甚至只是Java伪代码,对我来说都有很长的路要走。

java akka actor fsm
2个回答
23
投票

我认为我们可以比FSM文档(http://doc.akka.io/docs/akka/snapshot/java/lambda-fsm.html)的copypasta做得更好。首先,让我们稍微探讨一下您的用例。

您有两个触发器(或事件或信号) - powerOn和powerOff。您希望将这些信号发送给Actor并使其更改状态,其中两个有意义的状态为On和Off。

现在,严格来说FSM需要一个额外的组件:您希望在转换时采取的行动。

FSM:
State (S) x Event (E) -> Action (A), State (S')

Read: "When in state S, if signal E is received, produce action A and advance to state S'"

你不需要动作,但是演员不能直接检查,也不能直接修改。所有突变和确认都通过异步消息传递发生。

在您的示例中,它不提供在转换时执行的操作,您基本上拥有一个无操作的状态机。操作发生,状态转换没有副作用,并且该状态是不可见的,因此工作机器与损坏的机器相同。因为这一切都是异步发生的,所以你甚至都不知道破碎的东西何时完成。

因此,请允许我稍微扩展您的合同,并在您的FSM定义中包含以下操作:

 When in Off, if powerOn is received, advance state to On and respond to the caller with the new state
 When in On, if powerOff is received, advance state to Off and respond to the caller with the new state

现在我们可以构建一个实际可测试的FSM。

让我们为你的两个信号定义一对类。 (AbstractFSM DSL期望在课堂上匹配):

public static class PowerOn {}
public static class PowerOff {}

让我们为你的两个状态定义一对枚举:

 enum LightswitchState { on, off }

让我们定义一个AbstractFSM Actor(http://doc.akka.io/japi/akka/2.3.8/akka/actor/AbstractFSM.html)。扩展AbstractFSM允许我们使用与上述类似的FSM定义链定义一个actor,而不是直接在onReceive()方法中定义消息行为。它为这些定义提供了一个很好的小DSL,并且(有点奇怪)期望定义在静态初始化器中设置。

但是,快速绕道:AbstractFSM定义了两个泛型,用于提供编译时类型检查。

S是我们希望使用的State类型的基础,D是Data类型的基础。如果您正在构建一个能够保存和修改数据的FSM(可能是灯开关的功率计?),您可以构建一个单独的类来保存这些数据,而不是尝试将新成员添加到AbstractFSM的子类中。由于我们没有数据,让我们定义一个虚拟类,这样你就可以看到它是如何传递的:

public static class NoDataItsJustALightswitch {}

因此,通过这种方式,我们可以构建我们的actor类。

public class Lightswitch extends AbstractFSM<LightswitchState, NoDataItsJustALightswitch> {
    {  //static initializer
        startWith(off, new NoDataItsJustALightswitch()); //okay, we're saying that when a new Lightswitch is born, it'll be in the off state and have a new NoDataItsJustALightswitch() object as data

        //our first FSM definition
        when(off,                                //when in off,
            matchEvent(PowerOn.class,            //if we receive a PowerOn message,
                NoDataItsJustALightswitch.class, //and have data of this type,
                (powerOn, noData) ->             //we'll handle it using this function:
                    goTo(on)                     //go to the on state,
                        .replying(on);           //and reply to the sender that we went to the on state
            )
        );

        //our second FSM definition
        when(on, 
            matchEvent(PowerOff.class, 
                NoDataItsJustALightswitch.class, 
                (powerOn, noData) -> {
                    goTo(off)
                        .replying(off);
                    //here you could use multiline functions,
                    //and use the contents of the event (powerOn) or data (noData) to make decisions, alter content of the state, etc.
                }
            )
        );

        initialize(); //boilerplate
    }
}

我确定你在想:我怎么用这个?!因此,让我们使用直接JUnit和Akka Testkit for java来为您提供测试工具:

public class LightswitchTest { 
    @Test public void testLightswitch() {
        ActorSystem system = ActorSystem.create("lightswitchtest");//should make this static if you're going to test a lot of things, actor systems are a bit expensive
        new JavaTestKit(system) {{ //there's that static initializer again
            ActorRef lightswitch = system.actorOf(Props.create(Lightswitch.class)); //here is our lightswitch. It's an actor ref, a reference to an actor that will be created on 
                                                                                    //our behalf of type Lightswitch. We can't, as mentioned earlier, actually touch the instance 
                                                                                    //of Lightswitch, but we can send messages to it via this reference.

            lightswitch.tell(    //using the reference to our actor, tell it
                new PowerOn(),   //to "Power On," using our message type
                getRef());       //and giving it an actor to call back (in this case, the JavaTestKit itself)

            //because it is asynchronous, the tell will return immediately. Somewhere off in the distance, on another thread, our lightbulb is receiving its message

            expectMsgEquals(LightswitchState.on);   //we block until the lightbulb sends us back a message with its current state ("on.")
                                                     //If our actor is broken, this call will timeout and fail.

            lightswitch.tell(new PowerOff(), getRef());

            expectMsgEquals(LightswitchState.off);   

            system.stop(lightswitch); //switch works, kill the instance, leave the system up for further use
        }};
    }
}

你有:FSM lightswitch。但老实说,这个微不足道的例子并没有真正显示出FSM的强大功能,因为无数据的例子可以作为一组“成为/不成为”的行为来执行,只有一半的LoC没有泛型或lambda。更具可读性的IMO。

PS考虑学习Scala,如果只是为了能够阅读其他人的代码!本书的前半部分Atomic Sc​​ala可在线免费获取。

PPS如果您真正想要的是一个可组合的状态机,我维护Pulleys,一个基于纯java中状态图的状态机引擎。它已经存在多年(许多XML和旧模式,没有DI集成),但如果你真的想要将状态机的实现与输入和输出分离,那么可能会有一些灵感。


2
投票

我知道斯卡拉的演员。 这个Java Start Code可以帮助您继续:

是的,从SimpleFSM延长你的AbstractFSM。 该州是enumAbstractFSM。 你的<someArguments>可以是Data中的AbstractFSM部分 你的powerOnpowerOff是演员留言/活动。并且状态切换在转换部分中

// states
enum State {
  Off, On
}

enum Uninitialized implements Data {
  Uninitialized
}

public class SimpleFSM extends AbstractFSM<State, Data> {
    {
        // fsm body
        startWith(Off, Uninitialized);

        // transitions
        when(Off,
            matchEvent(... .class ...,
            (... Variable Names ...) ->
              goTo(On).using(...) ); // powerOn(<someArguments>)

        when(On,
            matchEvent(... .class ...,
            (... Variable Names ...) ->
              goTo(Off).using(...) ); // powerOff(<someArguments>)

        initialize();

    }
}

真正的工作项目见

Scala and Java 8 with Lambda Template for a Akka AbstractFSM

© www.soinside.com 2019 - 2024. All rights reserved.