2015-06-16 7 views
8

Nota: Sono uno sviluppatore Java senza conoscenza pratica di Scala (purtroppo). Vorrei chiedere che tutti gli esempi di codice forniti nella risposta utilizzino l'API Java di Akka.Akka Java FSM per esempio

Sto cercando di utilizzare l'API Akka FSM per modellare la seguente macchina di stato super-semplice. In realtà, la mia macchina è molto più complicata, ma la risposta a questa domanda mi consentirà di estrapolare il mio vero FSM.

enter image description here

E così ho 2 stati: Off e On. Puoi andare su Off -> On accendendo la macchina chiamando lo SomeObject#powerOn(<someArguments>). È possibile passare da On -> Off spegnendo la macchina chiamando SomeObject#powerOff(<someArguments>).

Mi chiedo quali attori e classi di supporto avrò bisogno per implementare questo FSM. I credo l'attore che rappresenta l'FSM deve estendere AbstractFSM. Ma quali classi rappresentano i 2 stati? Quale codice espone e implementa le transizioni di stato powerOn(...) e powerOff(...)? Un esempio di Java funzionante, o anche solo uno pseudo-codice Java, mi farebbe molto strada qui.

risposta

17

penso che possiamo fare un po 'meglio di copypasta dalla documentazione FSM (http://doc.akka.io/docs/akka/snapshot/java/lambda-fsm.html). Per prima cosa, esploriamo un po 'il caso d'uso.

Si dispone di due trigger (o eventi o segnali) - powerOn e powerOff. Vorresti inviare questi segnali a un attore e farlo cambiare stato, di cui i due stati significativi sono On e Off.

Ora, in senso stretto un FSM ha bisogno di un componente aggiuntivo: un'azione che si desidera intraprendere in transizione.

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

non si BISOGNO un'azione, ma un attore non può essere ispezionati direttamente, né direttamente modificato. Tutte le mutazioni e le conferme avvengono tramite il passaggio di messaggi asincroni.

Nel tuo esempio, che non fornisce alcuna azione da eseguire durante la transizione, in pratica hai una macchina a stati che è un no-op. Le azioni avvengono, le transizioni di stato senza effetti collaterali e quello stato è invisibile, quindi una macchina funzionante è identica a una macchina rotta.E poiché tutto ciò avviene in modo asincrono, non si sa nemmeno quando la cosa rotta è finita.

Così mi permettono di espandere il vostro contratto di un po ', e comprendono le seguenti azioni nelle definizioni 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 

Ora ci potrebbe essere in grado di costruire un FSM che in realtà è verificabile.

Definiamo una coppia di classi per i vostri due segnali. (L'AbstractFSM DSL prevede di abbinare il classe):

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

Definiamo un paio di enumerazioni per i vostri due stati:

enum LightswitchState { on, off } 

Definiamo un AbstractFSM Attore (http://doc.akka.io/japi/akka/2.3.8/akka/actor/AbstractFSM.html). L'estensione di AbstractFSM ci consente di definire un attore utilizzando una catena di definizioni FSM simile a quelle di cui sopra piuttosto che definire il comportamento del messaggio direttamente in un metodo onReceive(). Fornisce un piccolo DSL per queste definizioni e (un po 'stranamente) si aspetta che le definizioni vengano impostate in un inizializzatore statico.

Una breve deviazione, tuttavia: AbstractFSM ha due generici definiti che vengono utilizzati per fornire il controllo del tipo di tempo compilato.

S è la base dei tipi di stato che si desidera utilizzare e D è la base dei tipi di dati. Se stai costruendo un FSM che manterrà e modificherà i dati (forse un misuratore di potenza per il tuo interruttore della luce?), Dovresti creare una classe separata per contenere questi dati piuttosto che provare ad aggiungere nuovi membri alla sottoclasse di AbstractFSM. Dal momento che non abbiamo dati, definiamo una classe fittizia solo così si può vedere come viene passata in giro:

public static class NoDataItsJustALightswitch {} 

E così, con questo fuori del modo, possiamo costruire la nostra classe attore.

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 
    } 
} 

Sono sicuro che ti starai chiedendo: come lo uso ?! Quindi cerchiamo di un test harness utilizzando JUnit dritto e l'Akka TESTKIT per Java fare:

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 
     }}; 
    } 
} 

E ci sei: un lightswitch FSM. Onestamente, un esempio di questo banale non mostra realmente la potenza delle FSM, dato che un esempio senza dati può essere eseguito come un insieme di comportamenti "diventare/non-abili" in circa la metà di LoC senza generici o lambda. IMO molto più leggibile.

PS considera l'apprendimento di Scala, se non altro per leggere il codice di altre persone! La prima metà del libro Atomic Scala è disponibile gratuitamente online.

PPS se tutto ciò che si vuole veramente è una macchina a stati componibili, mantengo Pulleys, un motore di macchina di stato basato su statecharts in puro java. Si va avanti da anni (molto XML e vecchi schemi, nessuna integrazione DI) ma se si vuole davvero svincolare l'implementazione di una macchina a stati da ingressi e uscite, potrebbe esserci qualche fonte d'ispirazione.

+0

Grazie mille, ero nello stesso identico stato cercando di trovare un esempio java e questo è esattamente quello che stavo cercando –

+0

Non riuscivo a trovare il progetto github che hai citato, puoi pubblicare un link? –

+0

http://github.com/datamill-io/pulleys - è ora persino più vecchio di 3 anni e meno desiderabile. Sto progettando una nuova versione, probabilmente scritta in Scala 2.12, incentrata sull'implementazione delle attività (azioni asincronicamente attivate) e sui Submachines. –

2

Conosco gli attori in Scala.
Il presente Codice di avvio Java può aiutare, di andare avanti:

Sì, estendere le SimpleFSM da AbstractFSM.
Lo stato è un enum nello AbstractFSM.
tuo <someArguments> può essere la parte Data nel vostro AbstractFSM
tuo powerOn e powerOff sono Attore Messaggi/Eventi. e la commutazione di Stato si trova nella parte transizioni

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

    } 
} 

progetto reale di lavoro vedi

Scala and Java 8 with Lambda Template for a Akka AbstractFSM

+0

Grazie @Robert Halter (+1) - questo è un ottimo inizio ma sfortunatamente non lo sto ancora * completamente * comprendendo. Per la taglia, puoi rispondere ad alcuni followup? (1) Puoi sostituire tutti i tuoi elips ('...') con le chiamate effettive di codice/API? Ciò aiuterebbe davvero a legare tutto per me! (2) Puoi spiegare (anche solo aggiungendo javadocs/commenti sopra) lo scopo di 'Data' e' Uninitialized'? Sembrano inutili e inutili! – smeeb

+0

Infine (3) il 'SimpleFSM' sopra è confuso, perché tutto il suo codice si trova in un blocco di codice non etichettato (' {...} '). Come chiamerei questo codice dall'esterno? Puoi refactoring questo blocco di codice senza etichetta in "normali" metodi Java (in modo che io possa capire come chiamarli da * outside * the class)? Grazie ancora! – smeeb

+1

@smeeb qual è la tua intenzione dietro nelle chiamate di powerOn e powerOff? –