Comment mettre en œuvre une FSM - Machine D'état fini en Java

j'ai quelque chose à faire pour le travail et j'ai besoin de votre aide. Nous voulons implémenter un FSM - Finite State Machine , pour identifier la séquence de char(comme: A, B, C, A, C), et dire si elle a été acceptée.

nous pensons mettre en œuvre trois classes: State , Event et Machine . La classe state présente un noeud dans la classe FSM , nous avons pensé l'implémenter avec State design pattern , chaque noeud s'étendra de l'état de classe abstraite et chaque classe traitera différents types d'événements et d'indiquer la transition vers un nouvel état. Est-ce une bonne idée à votre avis?

Deuxièmement, nous ne savons pas comment sauvegarder toutes les transitions. Encore une fois , nous avons pensé à mettre en œuvre avec une sorte de map , qui tiennent le point de départ et obtient une sorte de vecteur avec les prochains États, mais je ne suis pas sûr que ce soit une bonne idée.

je serais heureux d'obtenir quelques idées sur la façon de le mettre en œuvre ou peut-être vous pouvez me donner quelques points de départ.

comment sauver la FSM, c'est-à-dire comment construire l'arbre au début du programme? Je l'ai googlé et trouvé beaucoup d'exemples, mais rien qui m'aide.

Merci beaucoup.

45
demandé sur cegas 2012-11-04 21:46:07

6 réponses

Le cœur d'une machine d'état est le tableau de transition, qui prend un état et un symbole (ce que vous appelez un événement) à un nouvel état. C'est juste un tableau d'États à deux index. Pour la santé mentale et la sécurité des caractères, déclarez les États et les symboles comme énumérations. J'ajoute toujours un membre" length " d'une certaine manière (spécifique à la langue) pour vérifier les limites du tableau. Quand j'ai codé à la main FSM's, je formate le code dans la ligne et le format de colonne avec le bidouillage d'espace de blanc. Les autres éléments d'une machine d'état sont état initial et l'ensemble de l'acceptation des états. La mise en œuvre la plus directe de l'ensemble des États accepteurs est un ensemble de booléens indexés par les États. En Java, cependant, les énumérations sont des classes, et vous pouvez spécifier un argument "accepting" dans la déclaration pour chaque valeur énumérée et l'initialiser dans le constructeur pour l'énumération.

Pour le type de machine, vous pouvez l'écrire comme une classe générique. Il faudrait deux types d'arguments, l'un pour les États et l'autre pour les États. les symboles, un argument tableau pour la table de transition, un État simple pour l'initiale. Le seul autre détail (bien que ce soit critique) est que vous devez appeler Enum.ordinal () pour obtenir un entier approprié pour indexer le tableau de transition, puisque vous n'avez pas de syntaxe pour déclarer directement un tableau avec un index d'énumération (bien qu'il devrait y en avoir).

pour anticiper un problème, EnumMap ne fonctionnera pas pour la table de transition, parce que la clé requise est une paire d'énumération valeurs, pas un seul.

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
    }
};
36
répondu eh9 2012-11-05 12:27:08

Hmm, je suggère que vous utilisez Flyweight pour mettre en œuvre les États. Objectif: éviter la mémoire aérienne d'un grand nombre de petits objets. Les machines d'état peuvent devenir très, très grandes.

http://en.wikipedia.org/wiki/Flyweight_pattern

Je ne suis pas sûr de voir la nécessité d'utiliser l'État design pattern pour implémenter les noeuds. Les nœuds dans une machine d'état sont apatrides. Ils correspondent juste le symbole d'entrée courant au transitions disponibles à partir de l'état actuel. C'est-à-dire, à moins que j'ai complètement oublié comment ils fonctionnent (ce qui est une possibilité certaine).

si je le codais, je ferais quelque chose comme ça:

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

Que ferait Flyweight dans ce cas? Eh bien, sous le FsmNode il y aurait probablement quelque chose comme ceci:

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

cela crée les objets D'État sur une base de besoin-To-use, il vous permet d'utiliser un beaucoup mécanisme sous-jacent plus efficace pour stocker la machine d'état réelle. Celui que j'utilise ici (Map(Entier, la Carte(Symbole, Integer))) n'est pas particulièrement efficace.

notez que la page Wikipedia se concentre sur les cas où de nombreux objets quelque peu similaires partagent les mêmes données, comme c'est le cas dans L'implémentation de chaînes de caractères en Java. À mon avis, Flyweight est un peu plus général, et couvre toute création à la demande d'objets avec une courte durée de vie (utiliser plus de CPU pour économiser sur un plus structure de données sous-jacente efficace).

8
répondu Anders Johansen 2012-11-05 11:52:24

EasyFSM est une bibliothèque Java dynamique qui peut être utilisée pour implémenter une FSM.

vous pouvez trouver la documentation pour le même à : Machine D'état fini en Java

aussi, vous pouvez télécharger la bibliothèque à : Java FSM Library: DynamicEasyFSM

8
répondu user2968375 2014-04-08 08:19:04

d'Envisager le facile, léger bibliothèque Java EasyFlow . De leurs docs:

avec EasyFlow vous pouvez:

  • implémenter une logique complexe mais garder votre code simple et propre
  • gérer les appels asynchrones avec facilité et élégance
  • éviter la concurrence en utilisant une approche de programmation axée sur les événements
  • éviter le débordement des piles erreur en évitant la récursion
  • pour simplifier la conception, la programmation et les tests de complexe d'applications java
7
répondu btiernay 2016-04-25 17:36:42

vous pouvez mettre en œuvre Machine D'état fini de deux façons différentes.

Option 1:

Machine à états finis avec un workflow prédéfini : recommandé si vous connaissez tous les États à l'avance et la machine d'état est presque fixe Sans aucun changement dans le futur

  1. Identifier tous les possibles états dans votre application

  2. identifiez tous les événements dans votre demande

  3. identifiez toutes les conditions dans votre application, qui peuvent conduire à une transition d'état

  4. de Survenance d'un événement qui peut provoquer des transitions d'état

  5. construire une machine à état fini par décider d'un flux de travail des états et des transitions.

    E. g si un événement 1 se produit à L'état 1, l'état sera mis à jour et l'état de la machine peut encore être dans l'état 1.

    si un événement 2 se produit à L'état 1, le système passera de l'état 1 à l'état 2

    1519160920"

cette conception est basée sur État et contexte motifs.

regarder Finite State Machine prototype classes.

Option 2:

arbres comportementaux: recommandé en cas de changements fréquents dans le flux de travail de la machine d'état. Vous pouvez dynamiquement ajouter un nouveau comportement sans briser l'arbre.

enter image description here

le la classe de base Task fournit une interface pour toutes ces tâches, les leaf tasks sont ceux qui viennent d'être mentionnés, et les tâches parentes sont les noeuds intérieurs qui décident quelle tâche exécuter ensuite.

les tâches n'ont que la logique dont elles ont besoin pour réellement faire ce qui est exigé d'elles, toute la logique de décision de savoir si une tâche a commencé ou non, si elle doit être mise à jour, si elle a terminé avec succès, etc. être groupé dans la classe TaskController , et ajouté par composition.

les décorateurs sont des tâches qui" décorent " une autre classe en l'enveloppant et en lui donnant une logique supplémentaire.

enfin, la classe Blackboard est une classe appartenant à L'IA mère à laquelle chaque tâche fait référence. Il fonctionne comme une base de données de connaissances pour toutes les tâches leaf

Avoir un coup d'oeil à cet article par Jaime Barrachina Verdia pour plus de détails

6
répondu Ravindra babu 2017-11-07 19:15:39

j'ai conçu et implémenté un exemple simple de machine d'état fini avec java.

Ifinitestatmachine : l'interface publique pour gérer la machine d'état fini

comme ajouter de nouveaux États à la machine d'état fini ou le transit vers les états suivants par

actions spécifiques.

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 : l'interface publique vers obtenir des informations relatives à l'état

comme le nom de l'état et les correspondances aux États connectés.

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

Action : la classe qui provoquera la transition des États.

public class Action {
    private String mActionName;

    public Action(String actionName) {
        mActionName = actionName;
    }

    String getActionName() {
        return mActionName;
    }

    @Override
    public String toString() {
        return mActionName;
    }

}

StateImpl : la mise en œuvre de L'État. J'ai appliqué la structure de données telle que HashMap pour garder les mappages D'Action-État.

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

}

Finitestatmachineimpl : Implementation of Ifinitestatmachine. J'utilise ArrayList pour garder tous les États.

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

exemple :

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

}

exemple complet sur Github

1
répondu shanwu 2018-08-03 17:15:26