Конечный автомат для простых переходов состояний

Я пытаюсь создать очень простой конечный автомат. Мой конечный автомат будет содержать следующие 3 состояния:

public enum States {
    PENDING,
    ACTIVE,
    DONE
}

Здесь возможны несколько переходов + начальные состояния, а именно:

Начальные состояния: PENDING or ACTIVEПереходы:

  1. PENDING -> ACTIVE
  2. PENDING -> DONE
  3. ACTIVE -> DONE

Я ищу подходы для представления этих состояний и возможный конечный автомат для управления переходами. Я рассмотрел основанный на перечислении подход, такой как этот, но я также хочу показать переходы состояний клиенту, и я не уверен, является ли это разумным в этом подходе.

Я также смотрел на другие методы, такие как State State, но кажется, что это может быть излишним для такого простого запроса.

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

4 ответа

Один из простых вариантов - сохранить переходы в виде I want to transition from X to Y while applying this function. Перечисления хорошо подходят для перечисления всех возможных / допустимых состояний в конечном автомате. Нам нужно что-то, чтобы удерживать наши переходы между состояниями - может быть,Map<StateType, StateType>? Но нам также нужны какие-тоState объект и способ его изменения - нам нужен Map<StateType, Map<StateType, Transition>>. Ниже приведен пример кода компиляции, с которого вы должны начать. Вы можете разоблачитьState объект, как вам нравится (может быть, сделать его неизменяемым?) и добавлять переходы на лету.

import java.util.EnumMap;
import java.util.Map;
import java.util.function.Function;

class StackruQuestion57661787 {
    enum StateType {
        PENDING,
        ACTIVE,
        DONE
    }

    //Made the name explicit here to ease readability
    public interface Transition extends Function<State, State> { }

    public static class State {
        public StateType type;
        //TODO: some real data to manipulate, or make it immutable
        public Object data;
    }

    public static class StateMachine {
        private final Map<StateType, Map<StateType, Transition>> transitions =
                new EnumMap<>(StateType.class);
        private State state;

        public StateMachine(State initialState) {
            this.state = initialState;
            for (StateType value : StateType.values()) {
                transitions.put(value, new EnumMap<>(StateType.class));
            }
        }

        public void addTransition(
                StateType input,
                StateType output,
                Transition transition
        ) {
            //TODO: handle collisions? multiple transitions for a given 
            // output statetype seems like a strange use-case
            transitions.get(input).put(output, transition);
        }

        public void moveTo(StateType toType) {
            Transition transition = transitions.get(state.type).get(toType);
            if (transition == null) {
                //TODO: handle me
                throw new RuntimeException();
            }
            //transition should modify the states "type" too OR
            //you implement it here
            state = transition.apply(state);
        }

        public State getState() {
            return state;
        }
    }
}

Вам нужно будет найти более сложное / абстрактное решение, если вы State тип объектов зависит от текущего StateType.

Если вы используете Spring, вы можете рассмотреть Spring Statemachine. https://projects.spring.io/spring-statemachine/

Я бы также посоветовал вам проверить два фреймворка, прежде чем создавать свой собственный конечный автомат. Теория конечного автомата действительно сложна для самостоятельной разработки, особенно не слишком упоминаемые концепции, такие как Sub / Nested State Machines, являются обязательными для сложных / успешных проектов State Machine.

Один упоминается выше Spring State Machine, а второй — Akka Finite State Machine.

Мой личный опыт Spring State Machine отлично подходит для моделирования таких вещей, как жизненный цикл приложения с такими состояниями, как ЗАПУСК, ИНИЦИАЛИЗАЦИЯ, РАБОТА, ОБСЛУЖИВАНИЕ, ОШИБКА, ВЫКЛЮЧЕНИЕ и т. д., но не так хорош для моделирования таких вещей, как диаграммы покупок. , резервирование, процессы одобрения кредита и т. д., хотя объем памяти слишком велик для моделирования миллионов экземпляров.

С другой стороны, Akka FSM занимает очень мало места, и я лично реализовал системы, содержащие миллионы экземпляров State Machine, и у него есть еще один инструмент, который полностью отсутствует в Spring State Machine. В современных ИТ одна вещь неизбежна: изменение: ни один рабочий процесс/процесс, который вы моделируете, не останется неизменным с течением времени, поэтому вам нужен механизм для интеграции этих изменений в ваши длительные рабочие процессы/процессы (что я имею в виду под этим, что происходит если у вас есть процесс, который был запущен до вашего последнего выпуска программного обеспечения и сохранялся со старой моделью, теперь у вас есть новый выпуск и модель изменена, вы должны прочитать сохраненный процесс и продолжить с новой моделью). Akka - встроенное решение этой проблемы Event/Schema Evolution .

Если вам нужны примеры реализации Spring State Machine, вы можете проверить следующий мой блог , примеры Akka FSM вы можете найти в следующих примерахBlog1, Blog2.

Надеюсь, это поможет.

У меня есть личный дизайн, который я широко использовал, который я называю "насос". В вашем классе конечных автоматов есть функция с именем "pump", которая оценивает состояние и обновляет его соответствующим образом. Каждая оценка состояния может потребовать некоторого ввода от внешнего источника (контроллеров), такого как пользователь или AI. Эти объекты необходимы при инициализации конечного автомата и обычно являются абстрактными реализациями. Затем вы добавляете обратные вызовы событий, которые приложения могут переопределять, чтобы перехватывать события. Одним из преимуществ этого подхода является способ "насоса", который может быть выполнен из однопоточной или многопоточной системы.

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

Тогда в вашем приложении вам нужно только предоставить правильные контроллеры в зависимости от ситуации.

Ниже приведен очень грубый конечный автомат для управления гипотетической игрой в кости. Я опустил большинство деталей, оставляя суть подхода. Обратите внимание, что одна реализация Player.rollDice может быть методом блокировки, который ожидает, когда пользователь нажмет кнопку, чтобы продвинуть игру. В этой схеме вся логика управления игрой содержится в машине и может быть протестирована независимо от любого пользовательского интерфейса.

interface Player {
   boolean rollDice();
}

class Game {
   int state;
   Player [] players;
   int currentPlayer;
   int dice;

   void pump() {
      switch (state) {
         case ROLL_DICE:
            if (players[currentPlayer].rollDice()) {
               dice = Math.rand() % 6 + 1;
               onDiceRolled(dice);
               state = TAKE_TURN;
            }
            break;
         case TAKE_TURN:
            ...
            break;
      }
   }

   // base method does nothing. Users can override to handle major state transitions.
   protected void onDiceRolled(int dice) {}
}
Другие вопросы по тегам