sehen Ich denke, wir ein bisschen besser als copypasta der FSM docs tun können (http://doc.akka.io/docs/akka/snapshot/java/lambda-fsm.html). Lassen Sie uns zuerst Ihren Anwendungsfall näher untersuchen.
Sie haben zwei Trigger (oder Ereignisse oder Signale) - PowerOn und PowerOff. Sie möchten diese Signale an einen Actor senden und diesen Status ändern lassen, von dem die zwei bedeutungsvollen Zustände Ein und Aus sind.
Genau genommen braucht ein FSM eine zusätzliche Komponente: eine Aktion, die Sie beim Übergang durchführen möchten.
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'"
Sie nicht NEED eine Aktion, sondern ein Schauspieler nicht direkt eingesehen werden kann, noch direkt modifiziert. Alle Mutationen und Bestätigungen erfolgen durch asynchrone Nachrichtenweitergabe.
In Ihrem Beispiel, das keine Aktion für den Übergang bereitstellt, haben Sie im Grunde eine Zustandsmaschine, die ein No-Op ist. Es treten Aktionen auf, Zustandsübergänge ohne Nebenwirkung und dieser Zustand ist unsichtbar, also ist eine Arbeitsmaschine identisch mit einer Gebrochenen.Und da dies alles asynchron geschieht, wissen Sie nicht einmal, wann das kaputte Ding fertig ist.
So erlauben Sie mir Ihren Vertrag ein wenig zu erweitern, und umfasst die folgenden Aktionen in der FSM Definitionen:
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
Jetzt könnten wir in der Lage sein, eine FSM zu bauen, die tatsächlich überprüfbar ist.
Lassen Sie uns ein Paar Klassen für Ihre beiden Signale definieren. (Der AbstractFSM DSL erwartet auf Klasse zu finden):
public static class PowerOn {}
public static class PowerOff {}
Lasst uns ein Paar von Aufzählungen für zwei Zustände definieren:
enum LightswitchState { on, off }
Lassen Sie uns ein AbstractFSM Schauspieler definieren (http://doc.akka.io/japi/akka/2.3.8/akka/actor/AbstractFSM.html). Durch die Erweiterung von AbstractFSM können wir einen Akteur definieren, der eine Kette von FSM-Definitionen verwendet, die den obigen ähnlich sind, anstatt das Nachrichtenverhalten direkt in einer onReceive() -Methode zu definieren. Es bietet eine nette kleine DSL für diese Definitionen und (etwas bizarr) erwartet, dass die Definitionen in einem statischen Initialisierer eingerichtet werden.
Ein kurzer Umweg, aber: AbstractFSM hat zwei Generika definiert, die verwendet werden, um die Kompilierzeit Typprüfung bereitzustellen.
S ist die Basis der Statustypen, die wir verwenden möchten, und D ist die Basis der Datentypen. Wenn Sie eine FSM erstellen, die Daten speichert und ändert (möglicherweise ein Leistungsmesser für Ihren Lichtschalter?), Würden Sie eine separate Klasse für diese Daten erstellen, anstatt zu versuchen, Ihrer Unterklasse von AbstractFSM neue Mitglieder hinzuzufügen. Da wir keine Daten haben, lassen Sie uns einfach eine Dummy-Klasse definieren, so können Sie sehen, wie es um übergeben wird:
public static class NoDataItsJustALightswitch {}
Und so, mit diesem aus dem Weg, können wir unsere Schauspieler Klasse bauen.
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
}
}
Ich bin mir sicher, dass Sie sich fragen: Wie benutze ich das ?! Also lassen Sie uns machen Sie ein Test-Harnisch gerade JUnit und die Akka Testkit für Java mit:
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
}};
}
}
Und da sind Sie ja: ein FSM Lichtschalter. Ehrlich gesagt, zeigt ein Beispiel dieses Trivial nicht wirklich die Stärke von FSMs, da ein datenfreies Beispiel als eine Menge von "Werden/Nicht-Bekommen" -Verhaltensweisen in ungefähr der Hälfte der LoCs ohne Generika oder Lambdas durchgeführt werden kann. Viel besser lesbar IMO.
PS erwägen, Scala zu lernen, wenn nur in der Lage sein, den Code anderer zu lesen! Die erste Hälfte des Buches Atomic Scala ist kostenlos online verfügbar.
PPS Wenn alles, was Sie wirklich wollen, eine zusammensetzbare Zustandsmaschine ist, behalte ich Pulleys, eine State Machine Engine basierend auf Statecharts in reinem Java. Es geht in Jahren voran (viel XML und alte Muster, keine DI-Integration), aber wenn Sie wirklich die Implementierung einer Zustandsmaschine von den Inputs und Outputs entkoppeln wollen, kann es dort einige Inspiration geben.
Vielen Dank, ich war in genau dem gleichen Zustand und versuchte ein Java-Beispiel zu finden und das ist genau das, was ich gesucht habe –
Ich konnte das Github-Projekt nicht finden, das du erwähnt hast, kannst du einen Link posten? –
http://github.com/datamill-io/pulleys - es ist jetzt sogar 3 Jahre älter und weniger wünschenswert. Ich entwerfe eine neue Version, die wahrscheinlich in Scala 2.12 geschrieben wurde und sich auf die Implementierung von Aktivitäten (asynchrone ausgelöste Aktionen) und Submachines konzentriert. –