2015-06-16 11 views
8

Bitte beachten Sie: Ich bin ein Java-Entwickler ohne Scala-Kenntnisse (leider). Ich würde verlangen, dass jedes Codebeispiel, das in der Antwort bereitgestellt wird, Akka's Java API verwendet.Akka Java FSM von Beispiel

Ich versuche, die Akka FSM API zu verwenden, um die folgende super-einfache Zustandsmaschine zu modellieren. In Wirklichkeit ist meine Maschine viel komplizierter, aber die Antwort auf diese Frage erlaubt mir, auf meine tatsächliche FSM zu extrapolieren.

enter image description here

Und so habe ich 2 heißt es: Off und On. Sie können von Off -> On gehen, indem Sie das Gerät einschalten, indem Sie SomeObject#powerOn(<someArguments>) anrufen. Sie können von On -> Off gehen, indem Sie die Maschine ausschalten, indem Sie SomeObject#powerOff(<someArguments>) anrufen.

Ich frage mich, welche Schauspieler und unterstützenden Klassen ich brauche, um diese FSM zu implementieren. I glaube, der Schauspieler, der die FSM darstellt, muss AbstractFSM erweitern. Aber welche Klassen repräsentieren die 2 Staaten? Welcher Code setzt die Zustandsübergänge powerOn(...) und powerOff(...) frei und implementiert sie? Ein funktionierendes Java-Beispiel oder auch nur Java-Pseudocode würde hier einen langen Weg zurücklegen.

Antwort

17

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.

+0

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 –

+0

Ich konnte das Github-Projekt nicht finden, das du erwähnt hast, kannst du einen Link posten? –

+0

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. –

2

Ich weiß über Schauspieler in Scala.
Diese Java Start Code kann Ihnen helfen, vorwärts zu gehen:

Ja, Ihre SimpleFSM von AbstractFSM zu erweitern.
Der Staat ist ein enum in der AbstractFSM.
Ihre <someArguments> kann die Data Teil in Ihrem AbstractFSM
Ihre powerOn und powerOff sind Schauspieler Nachrichten/Events. Und der Staat Schalt ist in der Transitionen Teil

// 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(); 

    } 
} 

Echtarbeitsprojekt

Scala and Java 8 with Lambda Template for a Akka AbstractFSM

+0

Danke @Robert Halter (+1) - das ist ein toller Anfang, aber leider verstehe ich das noch nicht ganz *. Können Sie bitte für die Prämie ein paar Nachfragen beantworten? (1) Können Sie alle Ihre Elips ('...') durch tatsächliche Code-/API-Aufrufe ersetzen? Das würde mir wirklich helfen, alles zusammen zu binden! (2) Können Sie bitte (auch nur durch Hinzufügen von Javadocs/Kommentaren) den Zweck von "Data" und "Uninitialized" erklären? Sie erscheinen nutzlos und unnötig! – smeeb

+0

Schließlich (3) ist das obige "SimpleFSM" verwirrend, weil sein gesamter Code in einem unlabeled Codeblock ('{...}') ist. Wie würde ich diesen Code von außen nennen? Kannst du diesen unmarkierten Code-Block in "normale" Java-Methoden umgestalten (damit ich verstehen kann, wie man sie von * außerhalb * der Klasse aufruft)? Nochmals vielen Dank! – smeeb

+1

@smeeb Was ist Ihre Absicht hinter in den Aufrufen von powerOn und powerOff? –