Как дочерний конечный автомат может вернуть управление родительскому конечному автомату?

9

У моего автомата верхнего уровня есть некоторые состояния и ребра. Я назову это родительским конечным автоматом.

A ----> B ----> C

Любое состояние внутри родительского конечного автомата также может быть конечным автоматом. Я назову этих детей государственными машинами.

           ___________
         /            \
A ----> |  B0->B1->B2  | ----> C
         \____________/

Если родительский конечный автомат переходит от A к B, конечный автомат B вступает во владение. После завершения работы B, как ему следует отказаться от управления родительским конечным автоматом и перейти в состояние C? Какой шаблон дизайна вы используете?

Если вам интересно, у меня есть дочерние конечные автоматы в родительских конечных автоматах, потому что мой точный проект довольно сложен, и естественно инкапсулировать внутреннюю работу дочернего состояния.

JoJo
источник
Я предполагаю, что B0, B1 и B2 должны знать, что они являются компонентами чего-то, что внешний мир считает единым целым. Поэтому, возможно, вам понадобится MachineContainerкласс для Bэтого, который содержит B0, B1 и B2, и когда B2 завершается, он передает управление обратно в свой контейнер, который затем переходит в C ... Хотя я никогда не пробовал ничего подобного. Это интересная проблема!
FrustratedWithFormsDesigner
2
Ваш вопрос имеет или очевидный ответ, или ваш вопрос не очень ясен. С точки зрения родителя, вы должны реализовать его точно так же, как если бы вы реализовали конечный автомат, у которого нет дочерних конечных автоматов. Просто так получается, что состояния реализуются с помощью дочерних конечных автоматов, но это никак не влияет на родителя. Это также не должно влиять на дочерние конечные автоматы, кроме того, что при выходе они генерируют только события родительского уровня.
Данк

Ответы:

5

Каждый конечный автомат имеет своего рода обработчик событий и средства для запуска этих событий. Этот обработчик принимает в качестве входных данных существующее состояние и тип события, выбирает новое состояние и при необходимости запускает некоторый код побочного эффекта.

По сути, находясь в состоянии B, ваш основной обработчик событий пересылает любые события, которые он не распознает, Bобработчику событий и остается в состоянии B. Когда Bтребуется перейти к C, он отправляет соответствующее событие в основной обработчик событий.

Карл Билефельдт
источник
2

Вы читали этот раздел Таупа ? Есть несколько различных способов сделать это, но многие из них зависят от того, как вы разбили свои конечные автоматы. Это отдельные процессы? Потоки? Объекты?

Выясните, как вы их построили, и посмотрите, есть ли для них канонический способ общения. Если его нет, возможно, вы неправильно проектируете свою систему.

Для меня я бы посмотрел на отдельные процессы, соединяя stdin и stdout вместе. Дочерний конечный автомат становится автономным, воздействуя на стандартный ввод и выводя на стандартный вывод. Это становится задачей родительского конечного автомата - запускать дочерний процесс, подключать каналы, а затем выгружать данные и ждать результатов. Все это уже сделано на всех современных языках, поэтому это должно быть легко сделать.

Спенсер Рэтбун
источник
1

Разделите два конечных автомата и используйте передачу сообщений между ними. Таким образом, конечный автомат 1 будет исходить из ABC, где в состоянии B он проверяет текущие результаты из конечного автомата 2. Если выходные данные изменились, конечный автомат 1 может учитывать его, и конечный автомат 2 не должен иметь никакой осведомленности о том, как конечный автомат 1 на самом деле работает. Что-то вроде:

typedef struct StateMachine {
  void(*Update)(); // function to update the state machine
  int Data;        // generic temp holder to survive state contexts
  int State;       // current state of our state machine
  int *Message;    // pointer to a shared integer for message passing
};

int main(void) {
  int Message = 0;
  /* NewStateMachine would malloc the struct, pass in the int reference
   * and function pointer as well as add it to a circularly linked list */
  NewStateMachine(&Message, MainLoop);
  NewStateMachine(&Message, MinorLoop);
  StateMachine *Current = StateMachine_CLL.First;

  for(;;) {
    Current->Update(Current); /* Update the current state machine */
    Current = Current->Next;  /* And the advance to the next one */
  }
}

void MainLoop(StateMachine *this) {
  switch(this.State) {
  case 0:
    CloseCoolantTank(1); /* safe to call if valve already closed */
    CloseCoolantTank(2); /* safe to call if valve already closed */
    this.State = 1;
    break;
  case 1:
    /* we have a message, do something */
    if(*this.Message) this.State = 2;          
    /* otherwise stall at this state until we get a message */
    else this.State = 1;          
    break;
  case 2:
    if(*this.Message == 1) this.State = 3;      /* warm */
    else if(*this.Message == 2) this.State = 4; /* hot! */
    else this.State = 0;                        /* cooled down, shut off valves */
    this.Message = 0;                           /* clear the message */
    break;
  case 3:
    OpenCoolantTank(1); /* opens the valve, safe to call if already open */
    this.State = 2;     /* recheck for new message */
    break;
  case 4:
    OpenCoolantTank(2); /* opens the valve, safe to call if already open */
    this.State = 3;     /* also open coolant tank 1 for extra cooling */
    break;
  }
}

/* Monitor temperature and send messages on overheat */
void MinorLoop(StateMachine *this) {
  switch(this.State) {
  case 0:
    this.Data = ReadADCValue();
    this.State = 1;
    break;
  case 1:
    if(this.Data > 150) *this.Message = 2;
    else if(this.Data > 100) *this.Message = 1;
    this.State = 0;
    break;
  }
}
Рене Казинс
источник
1

Решение зависит от того, 1) являются ли подсостояния A видимыми для подсостояний B. 2) Происходят ли AB и C из общего родителя. Если у них общий родитель и видимость универсальна, у вас не должно быть особых проблем при переходе из подсостояния B в подсостояние A.

Если вы изолировали их через пространства имен и / или A, B и C не имеют общего родителя, то лучше всего иметь внешний драйвер изменения состояния для машин A, B и C. Это можно сделать через обработчик событий. Просто найдите наблюдателя в A, который может прослушивать события, возникающие в B, и переходить в собственное подсостояние на основе события.

DPD
источник