Akka Java FSM на примере
обратите внимание: Я разработчик Java без рабочего знания Scala (к сожалению). Я бы попросил, чтобы любые примеры кода, приведенные в ответе, использовали Java API Akka.
Я пытаюсь использовать API Akka FSM для моделирования следующей супер-простой государственной машины. На самом деле моя машина намного сложнее, но ответ на этот вопрос позволит мне экстраполировать на мой фактический FSM.
и поэтому у меня есть 2 государства:Off
и On
. Вы можете пойти сюда Off -> On
при подключении машины на вызов SomeObject#powerOn(<someArguments>)
. Вы можете пойти от On -> Off
выключив машину, позвонив SomeObject#powerOff(<someArguments>)
.
мне интересно, какие актеры и вспомогательные классы мне понадобятся для реализации этого FSM. Я!--21-->верить актер, представляющий FSM, должен продлить AbstractFSM
. Но какие классы представляют 2 состояния? Какой код предоставляет и реализует powerOn(...)
и powerOff(...)
состояния? Работа Пример Java, или даже просто Java псевдо-код, будет идти долгий путь для меня.
2 ответов
Я думаю, что мы можем сделать немного лучше, чем copypasta из документов FSM (http://doc.akka.io/docs/akka/snapshot/java/lambda-fsm.html). Во-первых, давайте немного рассмотрим ваш вариант использования.
у вас есть два триггера (или события, или сигналы) -- powerOn и powerOff. Вы хотели бы послать эти сигналы актеру и заставить его изменить состояние, из которого два значимых состояния включены и выключены.
теперь, строго говоря, 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'"
не нужно действие, но актер не может быть непосредственно проверен или непосредственно изменен. Все мутации и подтверждения происходят посредством асинхронной передачи сообщений.
в вашем примере, который не предоставляет никаких действий для выполнения при переходе, у вас в основном есть государственная машина, которая не работает. Действия происходят, переходы состояния без побочного эффекта и это состояние невидимо, поэтому рабочая машина идентично сломанному. И поскольку все это происходит асинхронно, вы даже не знаете, когда сломанную вещь закончена.
поэтому позвольте мне немного расширить ваш контракт и включить следующие действия в ваши определения 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
теперь мы могли бы построить FSM, который на самом деле тестируемый.
давайте определим пару классов для двух сигналов. (AbstractFSM DSL ожидает совпадения по классу):
public static class PowerOn {}
public static class PowerOff {}
давайте определите пару перечислений для ваших двух состояний:
enum LightswitchState { on, off }
давайте определим актера AbstractFSM (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");//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
}};
}
}
а вот и вы: переключатель FSM lightswitch. Честно говоря, пример этого тривиального на самом деле не показывает силу FSMs, поскольку пример без данных может быть выполняется как набор" стать/неприличным " поведением в половине случаев без дженериков или лямбд. Гораздо более читаемый IMO.
PS рассмотрите возможность изучения Scala, хотя бы для того, чтобы читать код других людей! Первая половина книги Atomic Scala доступна бесплатно онлайн.
P. P. S. Если вы хотите компонуемого государственной машины, я утверждаю блоки, механизм государственной машины, основанный на statecharts в чистой java. Это происходит в годах (много XML и старые шаблоны, нет интеграции DI), но если вы действительно хотите отделить реализацию государственной машины от входов и выходов, там может быть некоторое вдохновение.
Я знаю об актерах в Scala.
Этот код запуска Java может помочь вам, чтобы идти вперед:
да, продлить SimpleFSM
С AbstractFSM
.
Государство-это enum
на AbstractFSM
.
Ваш <someArguments>
может быть Data
в своем AbstractFSM
Ваш powerOn
и powerOff
актер сообщений/событий.
И переключение состояния находится в части переходов
// 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();
}
}
реальный рабочий проект см.