Как реализовать машину конечного состояния FSM в Java
мне нужно кое-что сделать для работы, и мне нужна твоя помощь.
Мы хотим реализовать FSM - Finite State Machine
, чтобы определить последовательность символов(например: A, B, C, A, C) и сказать, принята ли она.
мы думаем реализовать три класса: State
, Event
и Machine
.
The state
класс представляет узел в FSM
, мы думали реализовать его с State design pattern
каждый узел будет простираться от абстрактного класса и каждый класс будет обрабатывать различные типы событий и указывают на переход к новой государство. По-вашему, это хорошая идея?
во-вторых, мы не знаем, как сохранить все переходы. Опять же, мы думали реализовать его с какой-то map
, которые держат начальную точку и получают какой-то вектор со следующими состояниями, но я не уверен, что это хорошая идея.
Я был бы рад получить некоторые идеи о том, как его реализовать, или, может быть, вы можете дать мне некоторые отправные точки.
как я должен сохранить FSM, то есть как я должен построить дерево в начале программы? Я погуглил его и нашел много примеров, но ничего, что помогло бы мне.
Спасибо большое.
6 ответов
сердцем машины состояний является таблица перехода, которая переводит состояние и символ (то, что вы называете событием) в новое состояние. Это всего лишь двухиндексный массив состояний. Для обеспечения безопасности типов и здравомыслия объявите состояния и символы перечислениями. Я всегда добавляю член "length" каким-то образом (для конкретного языка) для проверки границ массива. Когда я вручную закодировал FSM, я форматирую код в формате строки и столбца с помощью скрипки пробелов. Другими элементами государственной машины являются исходное состояние и набор принимающих состояний. Наиболее прямой реализацией множества принимающих состояний является массив логических значений, индексируемых состояниями. Однако в Java перечисления являются классами, и вы можете указать аргумент "accepting" в объявлении для каждого перечисляемого значения и инициализировать его в конструкторе для перечисления.
для типа машины вы можете записать его как универсальный класс. Для этого потребуется два аргумента типа: один для состояний и один для символы, аргумент массива для таблицы перехода, одно состояние для начального. Единственная другая деталь (хотя это критично) заключается в том, что вам нужно вызвать Enum.ordinal (), чтобы получить целое число, подходящее для индексирования массива перехода, так как у вас нет синтаксиса для прямого объявления массива с индексом перечисления (хотя должно быть).
чтобы предотвратить один вопрос,EnumMap
не будет работать для таблицы перехода, потому что требуемый ключ-это пара значений перечисления, а не один.
enum State {
Initial( false ),
Final( true ),
Error( false );
static public final Integer length = 1 + Error.ordinal();
final boolean accepting;
State( boolean accepting ) {
this.accepting = accepting;
}
}
enum Symbol {
A, B, C;
static public final Integer length = 1 + C.ordinal();
}
State transition[][] = {
// A B C
{
State.Initial, State.Final, State.Error
}, {
State.Final, State.Initial, State.Error
}
};
Хм, я бы предложил вам использовать Flyweight для реализации Штатов. Цель: избегайте накладных расходов памяти большого количества небольших объектов. Государственные машины могут стать очень, очень большими.
http://en.wikipedia.org/wiki/Flyweight_pattern
Я не уверен, что вижу необходимость использовать состояние шаблона проектирования для реализации узлов. Узлы в машине состояния не имеют состояния. Они просто сопоставляют текущий входной символ с доступными переходами из текущее состояние. То есть, если я полностью не забыл, как они работают (что является определенной возможностью).
если бы я кодировал его, я бы сделал что-то вроде этого:
interface FsmNode {
public boolean canConsume(Symbol sym);
public FsmNode consume(Symbol sym);
// Other methods here to identify the state we are in
}
List<Symbol> input = getSymbols();
FsmNode current = getStartState();
for (final Symbol sym : input) {
if (!current.canConsume(sym)) {
throw new RuntimeException("FSM node " + current + " can't consume symbol " + sym);
}
current = current.consume(sym);
}
System.out.println("FSM consumed all input, end state is " + current);
что будет делать Flyweight в этом случае? Ну, под FsmNode, вероятно, будет что-то вроде этого:
Map<Integer, Map<Symbol, Integer>> fsm; // A state is an Integer, the transitions are from symbol to state number
FsmState makeState(int stateNum) {
return new FsmState() {
public FsmState consume(final Symbol sym) {
final Map<Symbol, Integer> transitions = fsm.get(stateNum);
if (transisions == null) {
throw new RuntimeException("Illegal state number " + stateNum);
}
final Integer nextState = transitions.get(sym); // May be null if no transition
return nextState;
}
public boolean canConsume(final Symbol sym) {
return consume(sym) != null;
}
}
}
это создает объекты состояния на основе необходимости использования, это позволяет использовать гораздо более эффективный базовый механизм для хранения фактического состояния машины. Тот, который я использую здесь(Map(Integer, Map (Symbol, Integer))) не является особенно эффективным.
обратите внимание, что страница Википедии фокусируется на случаях, когда многие несколько похожие объекты разделяют аналогичные данные, как это имеет место в реализации строки в Java. На мой взгляд, Flyweight немного более общий и охватывает любое создание объектов по требованию с коротким сроком службы (Используйте больше CPU, чтобы сэкономить на более эффективной базовой структуре данных).
EasyFSM-это динамическая библиотека Java, которая может использоваться для реализации FSM.
вы можете найти документацию в : конечный автомат в Java
кроме того, вы можете скачать библиотека : библиотека Java FSM: DynamicEasyFSM
рассмотрим легкую, легкую библиотеку Java EasyFlow. Из их документов:
с EasyFlow вы можете:
- реализуйте сложную логику, но держите свой код простым и чистым
- обрабатывать асинхронные вызовы с легкостью и элегантностью
- избегайте параллелизма с помощью подхода программирования на основе событий
- избегайте ошибки StackOverflow, избегая рекурсии
- упростить конструкцию, программирование и тестирование сложных Java-приложений
вы можете реализовать конечную машину двумя различными способами.
Вариант 1:
конечный автомат с заранее определенным рабочим процессом : рекомендуется, если вы заранее знаете все состояния и состояние машины почти исправлено без каких-либо изменений в будущем
определить все возможные государства в приложении
определить все событий в вашей применение
определить все условия в вашем приложении, которое может привести к переходу состояния
возникновение события может вызвать переход государственной
-
построить конечную машину состояния, решив процесс из состояний и переходов.
e.g если событие 1 происходит в состоянии 1, состояние будет обновлено, а состояние компьютера может оставаться в состоянии 1.
Если событие 2 происходит в состоянии 1, при некоторой оценке условия система перейдет из состояния 1 в состояние 2
эта конструкция основана на государство и контекст шаблоны.
посмотреть Конечный Автомат классы прототипа.
Вариант 2:
поведенческие деревьев: порекомендованный если частые изменения к государственной машине рабочий процесс. Вы можете динамически добавлять новое поведение, не нарушая дерева.
базовый задание класс предоставляет интерфейс для всех этих задач задачи лист только что упомянуты, а родительские задачи-это внутренние узлы, которые решают, какую задачу выполнить дальше.
на задачи есть только логика, они должны на самом деле сделать то, что от них требуется, все логика решения независимо от того, запущена задача или нет, нужно ли ее обновлять, успешно ли она завершена и т. д. группируется в TaskController класс и добавлен по составу.
на декораторы задачи, которые "украшают" другой класс, обертывая его и давая ему дополнительную логику.
наконец,доски class-это класс, принадлежащий родительскому AI, на который ссылается каждая задача. Он работает как база данных знаний для всех лист задач
посмотри статьи by Хайме Barrachina Verdia для более подробной информации
я проектировал и реализовал простой пример машины конечного состояния с java.
IFiniteStateMachine: открытый интерфейс для управления конечным состоянием машины
такие, как добавление новых состояний в конечную машину или переход в следующие состояния по
конкретные действия.
interface IFiniteStateMachine {
void setStartState(IState startState);
void setEndState(IState endState);
void addState(IState startState, IState newState, Action action);
void removeState(String targetStateDesc);
IState getCurrentState();
IState getStartState();
IState getEndState();
void transit(Action action);
}
IState: публичный интерфейс для получения информации о состоянии
например, имя состояния и сопоставления с подключенными состояниями.
interface IState {
// Returns the mapping for which one action will lead to another state
Map<String, IState> getAdjacentStates();
String getStateDesc();
void addTransit(Action action, IState nextState);
void removeTransit(String targetStateDesc);
}
действие: класс, который вызовет переход состояний.
public class Action {
private String mActionName;
public Action(String actionName) {
mActionName = actionName;
}
String getActionName() {
return mActionName;
}
@Override
public String toString() {
return mActionName;
}
}
StateImpl: реализация IState. Я применил структуру данных, такую как HashMap чтобы сохранить отображение состояния действия.
public class StateImpl implements IState {
private HashMap<String, IState> mMapping = new HashMap<>();
private String mStateName;
public StateImpl(String stateName) {
mStateName = stateName;
}
@Override
public Map<String, IState> getAdjacentStates() {
return mMapping;
}
@Override
public String getStateDesc() {
return mStateName;
}
@Override
public void addTransit(Action action, IState state) {
mMapping.put(action.toString(), state);
}
@Override
public void removeTransit(String targetStateDesc) {
// get action which directs to target state
String targetAction = null;
for (Map.Entry<String, IState> entry : mMapping.entrySet()) {
IState state = entry.getValue();
if (state.getStateDesc().equals(targetStateDesc)) {
targetAction = entry.getKey();
}
}
mMapping.remove(targetAction);
}
}
FiniteStateMachineImpl: реализация IFiniteStateMachine. Я использую ArrayList, чтобы сохранить все штат.
public class FiniteStateMachineImpl implements IFiniteStateMachine {
private IState mStartState;
private IState mEndState;
private IState mCurrentState;
private ArrayList<IState> mAllStates = new ArrayList<>();
private HashMap<String, ArrayList<IState>> mMapForAllStates = new HashMap<>();
public FiniteStateMachineImpl(){}
@Override
public void setStartState(IState startState) {
mStartState = startState;
mCurrentState = startState;
mAllStates.add(startState);
// todo: might have some value
mMapForAllStates.put(startState.getStateDesc(), new ArrayList<IState>());
}
@Override
public void setEndState(IState endState) {
mEndState = endState;
mAllStates.add(endState);
mMapForAllStates.put(endState.getStateDesc(), new ArrayList<IState>());
}
@Override
public void addState(IState startState, IState newState, Action action) {
// validate startState, newState and action
// update mapping in finite state machine
mAllStates.add(newState);
final String startStateDesc = startState.getStateDesc();
final String newStateDesc = newState.getStateDesc();
mMapForAllStates.put(newStateDesc, new ArrayList<IState>());
ArrayList<IState> adjacentStateList = null;
if (mMapForAllStates.containsKey(startStateDesc)) {
adjacentStateList = mMapForAllStates.get(startStateDesc);
adjacentStateList.add(newState);
} else {
mAllStates.add(startState);
adjacentStateList = new ArrayList<>();
adjacentStateList.add(newState);
}
mMapForAllStates.put(startStateDesc, adjacentStateList);
// update mapping in startState
for (IState state : mAllStates) {
boolean isStartState = state.getStateDesc().equals(startState.getStateDesc());
if (isStartState) {
startState.addTransit(action, newState);
}
}
}
@Override
public void removeState(String targetStateDesc) {
// validate state
if (!mMapForAllStates.containsKey(targetStateDesc)) {
throw new RuntimeException("Don't have state: " + targetStateDesc);
} else {
// remove from mapping
mMapForAllStates.remove(targetStateDesc);
}
// update all state
IState targetState = null;
for (IState state : mAllStates) {
if (state.getStateDesc().equals(targetStateDesc)) {
targetState = state;
} else {
state.removeTransit(targetStateDesc);
}
}
mAllStates.remove(targetState);
}
@Override
public IState getCurrentState() {
return mCurrentState;
}
@Override
public void transit(Action action) {
if (mCurrentState == null) {
throw new RuntimeException("Please setup start state");
}
Map<String, IState> localMapping = mCurrentState.getAdjacentStates();
if (localMapping.containsKey(action.toString())) {
mCurrentState = localMapping.get(action.toString());
} else {
throw new RuntimeException("No action start from current state");
}
}
@Override
public IState getStartState() {
return mStartState;
}
@Override
public IState getEndState() {
return mEndState;
}
}
пример:
public class example {
public static void main(String[] args) {
System.out.println("Finite state machine!!!");
IState startState = new StateImpl("start");
IState endState = new StateImpl("end");
IFiniteStateMachine fsm = new FiniteStateMachineImpl();
fsm.setStartState(startState);
fsm.setEndState(endState);
IState middle1 = new StateImpl("middle1");
middle1.addTransit(new Action("path1"), endState);
fsm.addState(startState, middle1, new Action("path1"));
System.out.println(fsm.getCurrentState().getStateDesc());
fsm.transit(new Action(("path1")));
System.out.println(fsm.getCurrentState().getStateDesc());
fsm.addState(middle1, endState, new Action("path1-end"));
fsm.transit(new Action(("path1-end")));
System.out.println(fsm.getCurrentState().getStateDesc());
fsm.addState(endState, middle1, new Action("path1-end"));
}
}