Akka Java FSM实例讲解

10
请注意:我是一名Java开发人员,没有Scala的工作经验(可悲)。我希望任何在回答中提供的代码示例都使用Akka的Java API。
我正在尝试使用Akka FSM API来模拟以下超级简单的状态机。实际上,我的状态机要复杂得多,但是这个问题的答案将使我能够推断出实际FSM的解决方案。
因此,我有2个状态:OffOn。您可以通过调用SomeObject#powerOn(<someArguments>)打开机器,从而从Off -> On。您可以通过调用SomeObject#powerOff(<someArguments>)关闭机器,从而从On -> Off
我想知道为了实现这个状态机,我需要哪些actors和支持类。我相信代表FSM的actor必须扩展AbstractFSM。但是,哪些类代表这两个状态?什么代码公开并实施powerOn(...)powerOff(...)状态转换?一个可行的Java示例甚至只有Java伪代码对我来说都会有很大的帮助。
3个回答

23
我认为我们可以比从FSM文档中复制粘贴(http://doc.akka.io/docs/akka/snapshot/java/lambda-fsm.html)做得更好。首先,让我们更深入地探讨一下您的用例。
您有两个触发器(或事件或信号)-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'"

您不需要一个动作,但是Actor不能直接检查,也不能直接修改。所有的变异和确认都通过异步消息传递进行。

在您的示例中,没有提供要执行的操作,您基本上拥有一个空操作的状态机。操作发生,状态转换没有副作用,该状态是不可见的,因此工作机与损坏机相同。由于这一切都是异步发生的,您甚至不知道损坏的事情何时完成。

因此,请允许我稍微扩展一下您的合同,并在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

现在,我们也许可以构建一个可测试的有限状态机。
我们需要为你的两个信号定义一对类。(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定义来定义演员,而不是直接在onReceive()方法中定义消息行为。它为这些定义提供了一个不错的DSL,并且(有点奇怪地)期望这些定义在静态初始化程序中设置。
然而,快速地离题一下:AbstractFSM有两个泛型定义,用于提供编译时类型检查。
S是我们希望使用的状态类型的基础,D是数据类型的基础。如果您正在构建将保存和修改数据的FSM(也许是您的灯开关的电表?),则应构建一个单独的类来保存此数据,而不是尝试向AbstractFSM子类添加新成员。由于我们没有数据,因此让我们定义一个虚拟类,以便您可以看到它如何传递。
public static class NoDataItsJustALightswitch {}

现在,这个问题解决了,我们可以构建我们的演员类。

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为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开关例子。但是,说实话,这个简单的例子并不能真正展示FSMs的威力,因为可以使用一组"become/unbecome"行为在更少的LoC中执行无数据的例子,而不需要泛型或Lambda。在我看来,这样更易读。
另外,考虑学习Scala,即使只是为了能够阅读其他人的代码!《Atomic Scala》这本书的前半部分可以在网上免费阅读。
如果你真的只想要一个可组合的状态机,我维护着Pulleys,它是一个基于纯Java状态图的状态机引擎。它已经有些年头了(有很多XML和旧模式,没有DI集成),但如果你真的想将状态机的实现与输入和输出解耦,那么可能会有一些启发。

非常感谢,我也处于同样的状态,一直在寻找Java示例,这正是我所需要的。 - user_mda
我找不到你提到的 Github 项目,你能发个链接吗? - user_mda
http://github.com/datamill-io/pulleys -- 现在已经过去了3年,变得不那么受欢迎了。我正在设计一个新版本,可能是用Scala 2.12编写的,专注于实现活动(异步触发的操作)和子机器。 - Matthew Mark Miller
对我来说,system.actorOf(Props.create(Lightswitch.class)) 报告了一个未知的 actor 创建错误。这个 actor 是否也需要扩展 AbstractActor 类? - user_mda
你能够在状态传递数据吗?例如,如果我想根据演员的状态更改数据? - user_mda

2

我了解Scala中的Actor。
这个Java起始代码可能会对你有所帮助:

是的,将你的SimpleFSMAbstractFSM继承。
状态是在AbstractFSM中的一个enum
你的<someArguments>可以是你的AbstractFSM中的Data部分。
你的powerOnpowerOff是Actor消息/事件。 状态切换在转换部分。

// 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和Java 8的Lambda模板,用于Akka抽象FSM


最后,上面的SimpleFSM很令人困惑,因为它的所有代码都在一个未标记的代码块({...})中。我如何从外部调用这段代码?您能否将此未标记的代码块重构为“正常”的Java方法(以便我可以理解如何从类外部调用它们)?再次非常感谢! - smeeb
1
@smeeb,你在powerOn和powerOff的调用中使用<someArguments>的意图是什么? - Robert Halter
Halter 我认为我使用 <some arguments> 的目的只是为了从外部世界向FSM提供数据/信息,以帮助它做出决策。但在这个简单的“Power On, Power Off” FSM中,需要 <some arguments> 可能会误导并且完全没有必要!我想我是在考虑到更复杂的FSM,其中,除了简单的布尔值如“Power On”之外,我可能需要在机器可以进行状态转换之前向其提供一些额外的信息。 - smeeb
至少,在我的原始问题中,我提供了<一些参数>,因为我以前从未使用过FSM,无论是Akka还是其他。但请将其视为输入到FSM以帮助它决定是否更改状态。再次感谢! - smeeb
1
从外部,您可以调用actorRef.tell("MSG",null);其中MSG是Message对象。此Message对象在matchEvent中与类和变量名称进行匹配检查。 - Robert Halter
显示剩余2条评论

0

虽然这是一个非常古老的问题,但如果你通过谷歌搜索得到了它,而且你仍然对使用 Akka 实现 FSM 感兴趣,我建议你查看文档的 这一部分

如果你想要看一个实用的模型驱动状态机实现,可以看看我的 博客1博客2


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接