Question

i fait une machine d'état et je voudrais à tirer parti des génériques en Java. actuellement je ne vois pas la façon dont je peux faire ce travail et obtenir très Code recherche. Je suis sûr que ce problème de conception a été approché plusieurs fois avant, et je cherche une entrée. Heres une esquisse.

class State { ... }

une seule copie de chaque objet d'état distinct (la plupart des classes anonymes liées à des variables statiques finales), il a des données personnalisées pour chaque état. chaque objet d'état a un parent d'état (il y a un état de racine)

class Message { ... } 

chaque message est créé séparément et chacun a des données personnalisées. ils peuvent sous-classe les uns des autres. il y a une classe de message racine.

class Handler { ... } 

chaque gestionnaire est créé une seule fois et traite d'un état spécifique / combo message.

class StateMachine { ... }

maintient actuellement trace de l'état actuel, et une liste de tous (State, Message) -> mappings de Handler. il a d'autres fonctionnalités aussi bien. Je suis en train de garder cette classe générique et sous-classe avec des paramètres de type utilisé comme un tas de fois dans mon programme, et chaque fois avec un autre ensemble de son de Message de / la / State et Handler. différentes années StateMachine auront différents paramètres à leurs gestionnaires.

Approche A

ont la machine d'état garder une trace de toutes les applications.

class StateMachine<MH extends MessageHandler> {
  static class Delivery {
    final State state;
    final Class<? extends Message> msg;
  }
  HashMap<Delivery, MH> delegateTable;
  ...
}

class ServerStateMachine extends StateMachine<ServerMessageHandler> {
  ...
}

me permet d'avoir des méthodes de gestionnaire personnalisé pour cette machine d'état particulier. les paramètres de la méthode de handler.process peuvent être écrasés. Cependant, le gestionnaire ne peut pas être paramétré par le type de message.

Problème:. Cela implique l'utilisation de la santé mentale instanceof cocher pour chaque gestionnaire de messages (en vous assurant qu'il devient le message qu'il attend)

Approche B

permet de faire de chaque gestionnaire de messages paramétrés par type de message

class MessageHandler<M extends Message> {
  void process(M msg) { .... }
}

Problème: effacement de type me empêcher de stocker ces derniers dans une belle hashmap puisque toutes les années MessageHandler seront dactylographiés différemment. si je peux les stocker dans une carte, je ne serai pas en mesure de les retrouver et de les appeler avec les arguements appropriés.

Approche C

ont l'objet d'état gérer tous les messages.

class State<M extends Message> { ... }
class ServerState<M extends ServerMessage> extends State<M> { ... }

i ont des gestionnaires de messages liés aux états de la machine d'état spécifiques (en les mettant à l'intérieur), (chaque instance d'une machine d'Etat aurait sa propre liste d'états valides), ce qui permet aux gestionnaires d'être d'un type spécifique. (Machine d'état de serveur -> gestionnaire message serveur).

Problème: chaque Etat ne peut traiter un type de message. vous perdez aussi l'idée qui peut gérer différents messages que les états de l'enfant de l'état parent. l'effacement de type empêche également le StateMachine d'appeler les états actuels méthodes de traitement.

Approche D

ont eux-mêmes le processus du message en fonction de l'état.

Problème: jamais vraiment considéré, étant donné que chaque message doit avoir un gestionnaire différent en fonction de l'état de la machine d'état actuel. l'expéditeur ne saura pas l'état du StateMachine actuel.

Approche E

oublier génériques et code d'état dur / le traitement des messages avec une instruction switch.

Problème: bon sens

Solution non sécurisée:

Merci pour vos commentaires tout le monde, je pense que le problème a été que je ne réduisait pas ce problème à bon (trop de discussions) Heres ce que j'ai maintenant.

public class State { }

public class Message { }

public class MessageHandler<T extends Message> { }

public class Delivery<T extends Message> {
  final State state;
  final Class<T> msgClass;
}

public class Container {

  HashMap<Delivery<? extends Message>, MessageHandler<? extends Message>> table;

  public <T extends Message> add(State state, Class<T> msgClass, MessageHandler<T> handler) {
    table.put(new Delivery<T>(state, msgClass), handler);
  }

  public <T extends Message> MessageHandler<T> get(State state, T msg) {
    // UNSAFE - i cannot cast this properly, but the hashmap should be good
    MessageHandler<T> handler = (MessageHandler<T>)table.get(new Delivery<T>(state, msg.getClass()));
    return handler;
  }

}
Était-ce utile?

La solution

Pour l'approche B, ne pas utiliser un hashmap « agréable ». Au lieu de cela, écrire un gestionnaires de mappage des conteneurs de Typesafe aux objets hétérogènes classe:

interface Handler<T extends Message> {
...}


interface Message {...}

interface HandlerContainer {

    <T extends Message> void register(Class<T> clazz, Handler<T> handler);

    <T extends Message> Handler<T> getHandler(T t);

}


class HandlerContainerImpl implements HandlerContainer {

    private final Map<Class<?>,Handler<?>> handlers = new HashMap<Class<?>,Handler<?>>();

    <T extends Message> void register(Class<T> clazz, Handler<T> handler) {
          if (clazz==null || handler==null) {
             throw new IllegalArgumentException();
          }
          handlers.put(clazz,handler);
    }

    //Type safety is assured by the register message and generic bounds
    @SuppressWarnings("unchecked")
    <T extends Message> Handler<T> getHandler(T t) {
            return  (Handler<T>)handlers.get(t.getClass());

    }

}

Autres conseils

E avec énumérations et messages représentant des états est probablement le plus facile. Mais il est pas très extensible.

C en utilisant le modèle des visiteurs dans les classes de l'Etat à être envoyé sur le type de message ressemble, il peut être le meilleur de vos options. Étant donné le choix entre instanceof et des visiteurs, je pense que des visiteurs est un peu plus propre (quoique toujours maladroit). Les problèmes d'effacement de type ne posent une difficulté notable, et le traitement dans le message semble un peu en arrière. notation machine d'état typique a les états comme le centre de contrôle. De plus, vous pouvez avoir la classe abstraite pour les visiteurs types de messages jeter une erreur sur tous les Etats, ce qui permet aux États de se repli d'erreur sur les messages non valides gratuitement.

C + visiteur serait tout à fait analogue à l'approche que j'utilise fréquemment la mise en œuvre des machines d'état en C ou langues avec des fonctions de première classe - « Etat » est représenté par un pointeur sur la gestion des messages fonction dans l'état actuel, avec cette fonction renvoyant un pointeur vers la fonction d'état suivant (éventuellement lui-même). La boucle de commande de machine d'état va chercher simplement le message suivant, passe à la fonction de l'état actuel, et met à jour la notion de « courant » quand il retourne.

Approche E. Oubliez génériques, et utiliser des interfaces.

class Message { ... }
class State { ... }

class Machine {
  static State handle(State current, Message msg) {
    ...
  }
}

class CustomMessage extends Message { ... }
class CustomState extends State { ... }

class CustomMachine {
  static CustomState handle(CustomState current, CustomMessage msg) {
    // custom cases
    ...

    // default: generic case
    return Machine.handle(current, msg);
  }
}

L'approche que je l'ai vu dans quelques endroits, est d'utiliser les annotations. Utilisez les classes POJO régulières et les annoter à traiter par une classe de type de gestionnaire qui gère la machine d'état:

public class MyState {
 @OnEntry
 public void startStuff() {
  ...
 }

 @OnExit() 
 public void cleanup() {
  ..
 } 
}

Il y a quelques implémentations plus développées, je pense que la boîte à outils scientifique un était bon, mais je ne peux pas trouver le lien en ce moment: http://mina.apache.org/introduction-to-mina-statemachine. html http://weblogs.java.net/blog/carcassi/ archives / 2007/02 / finite_state_ma_1.html http://hubris.ucsd.edu/shared/manual.pdf

Approche F:

Oubliez génériques sauf si vous avez des modèles spécifiques de type. Définir un couple d'interfaces par votre système souhaité, y compris peut-être quelque chose comme

interface StateMachineState<R extends StateMachineState,T> {
    /* returns next state */
    R execute(T otherState); 
}

et pour une machine d'état particulier, utiliser les énumérations qui étendent StateMachineState:

class OtherState {
    public double x1;
    public int i;
}

enum MyState extends StateMachineState<MyState,OtherState>
{
    FOO {
       MyState execute(OtherState otherState) { 
           otherState.x1 += 3.0;
           otherState.i++;
           return BAR;
       }
    },
    BAR {
       MyState execute(OtherState otherState) { 
           otherState.x1 -= 1.0;
           otherState.i--;
           return (i % 3 == 0) ? FOO : BAR;
       }
    },         
}

Ensuite, vous pouvez faire quelque chose comme:

MyState state = MyState.FOO;
OtherState otherState = new OtherState();
otherState.i = 77;
otherState.x1 = 3.14159;
while (true)
{
    state = state.execute(otherState);
    /* do something else here */        
}

(mise en garde: code ne revérifié pour les erreurs de syntaxe)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top