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