Akka Java FSM par exemple

Remarque: je suis un développeur Java qui n’a aucune connaissance pratique de Scala (malheureusement). Je demanderais que tous les exemples de code fournis dans la réponse utilisent l’API Java d’Akka.

J’essaie d’utiliser l’API Akka FSM pour modéliser la machine à états super simple suivante. En réalité, ma machine est beaucoup plus compliquée, mais la réponse à cette question me permettra d’extrapoler à mon FSM actuel.

entrez la description de l'image ici

Et donc j’ai 2 états: Off et On . Vous pouvez aller Off -> On en allumant la machine en appelant SomeObject#powerOn() . Vous pouvez SomeObject#powerOff() On -> Off en éteignant la machine en appelant SomeObject#powerOff() .

Je me demande de quels acteurs et classes de soutien ai-je besoin pour mettre en œuvre ce FSM. Je crois que l’acteur représentant le FSM doit élargir AbstractFSM . Mais quelles classes représentent les 2 états? Quel code expose et implémente les transitions d’état powerOn(...) et powerOff(...) ? Un exemple Java fonctionnel, ou même simplement un pseudo-code Java, me serait très utile.

    Je pense que nous pouvons faire un peu mieux que copypasta des documents FSM ( http://doc.akka.io/docs/akka/snapshot/java/lambda-fsm.html ). Tout d’abord, examinons un peu votre cas d’utilisation.

    Vous avez deux déclencheurs (ou événements ou signaux) – powerOn et powerOff. Vous voudriez envoyer ces signaux à un acteur et le faire changer d’état, dont les deux états significatifs sont On et Off.

    Maintenant, à proprement parler, un FSM a besoin d’un composant supplémentaire: une action que vous souhaitez entreprendre lors de la transition.

     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'" 

    Vous n’avez pas besoin d’action, mais un acteur ne peut pas être directement inspecté, ni directement modifié. Toutes les mutations et tous les acquittements se produisent par le biais de la transmission asynchrone de messages

    Dans votre exemple, qui ne fournit aucune action à effectuer lors de la transition, vous avez essentiellement une machine à états qui ne fonctionne pas. Des actions se produisent, des transitions d’états sans effets secondaires et cet état est invisible, ainsi une machine en fonctionnement est identique à une machine en panne. Et comme tout cela se produit de manière asynchrone, vous ne savez même pas quand la chose cassée est terminée.

    Permettez-moi donc d’étendre un peu votre contrat et d’inclure les actions suivantes dans vos définitions 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 

    Maintenant, nous pourrions peut-être construire un FSM réellement testable.

    Définissons une paire de classes pour vos deux signaux. (le DSL AbstractFSM s’attend à correspondre à la classe):

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

    Définissons une paire d’énums pour vos deux états:

      enum LightswitchState { on, off } 

    Définissons un acteur AbstractFSM ( http://doc.akka.io/japi/akka/2.3.8/akka/actor/AbstractFSM.html ). L’extension de AbstractFSM nous permet de définir un acteur à l’aide d’une chaîne de définitions FSM similaires à celles ci-dessus plutôt que de définir le comportement d’un message directement dans une méthode onReceive (). Il fournit un joli petit DSL pour ces définitions, et attend (de façon bizarre) que les définitions soient configurées dans un initialiseur statique.

    Un détour rapide cependant: AbstractFSM a défini deux génériques qui permettent de vérifier le type de temps de compilation.

    S est la base des types d’état que nous souhaitons utiliser et D est la base des types de données. Si vous construisez un FSM qui contiendra et modifiera les données (peut-être un compteur de puissance pour votre interrupteur?), Vous devez créer une classe distincte pour conserver ces données plutôt que d’essayer d’append de nouveaux membres à votre sous-classe de AbstractFSM. Comme nous n’avons pas de données, définissons une classe factice afin que vous puissiez voir comment elle est transmise:

     public static class NoDataItsJustALightswitch {} 

    Et ainsi, avec cela, nous pouvons construire notre classe d’acteurs.

     public class Lightswitch extends AbstractFSM { { //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 } } 

    Je suis sûr que vous vous demandez: comment puis-je utiliser cela?! Faisons donc de vous un harnais de test utilisant straight JUnit et le kit de test Akka pour 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 }}; } } 

    Et vous voilà: un commutateur de lumière FSM. Honnêtement, cependant, cet exemple sortingvial ne montre pas vraiment la puissance des FSM, car un exemple sans données peut être interprété comme un ensemble de comportements “deviens / inconvenants” dans un nombre de fois égal à la moitié, sans générique ni lambda. Beaucoup plus lisible IMO.

    PS envisage d’apprendre Scala, ne serait-ce que pour pouvoir lire le code des autres! La première moitié du livre Atomic Scala est disponible gratuitement en ligne.

    PPS Si tout ce que vous voulez vraiment est une machine à états composable, je maintiens Pulleys , un moteur de machine à états basé sur des statistiques en java pur. Cela prend des années (beaucoup de XML et de vieux modèles, pas d’intégration DI), mais si vous voulez vraiment découpler l’implémentation d’une machine à états des entrées et des sorties, cela peut vous inspirer.

    Je connais des acteurs à Scala.
    Ce code de démarrage Java peut vous aider, pour aller de l’avant:

    Oui, SimpleFSM votre SimpleFSM partir de AbstractFSM .
    L’État est une enum dans AbstractFSM .
    Votre peut être la partie Data de votre AbstractFSM
    Vos powerOn et powerOff sont des messages / événements d’acteur. Et le changement d’état est dans la partie transitions

     // states enum State { Off, On } enum Uninitialized implements Data { Uninitialized } public class SimpleFSM extends AbstractFSM { { // fsm body startWith(Off, Uninitialized); // transitions when(Off, matchEvent(... .class ..., (... Variable Names ...) -> goTo(On).using(...) ); // powerOn() when(On, matchEvent(... .class ..., (... Variable Names ...) -> goTo(Off).using(...) ); // powerOff() initialize(); } } 

    Projet de travail réel voir

    Scala et Java 8 avec modèle Lambda pour un Akka AbstractFSM