problème de conception de génériques java (machine à états)

J’ai créé une machine à états et j’aimerais qu’elle tire parti des génériques en java. Actuellement, je ne vois pas la façon dont je peux faire ce travail et obtenir joli code. Je suis sûr que ce problème de conception a déjà été abordé à maintes resockets et je cherche des idées. Heres une ébauche.

class State { ... } 

une seule copie de chaque object d’état distinct (principalement des classes anonymes liées à des variables finales statiques), des données personnalisées sont associées à chaque état. chaque object d’état a un parent d’état (il y a un état racine)

 class Message { ... } 

chaque message est créé séparément et chacun a des données personnalisées. ils peuvent se sous-classer. il existe une classe de message racine.

 class Handler { ... } 

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

 class StateMachine { ... } 

conserve actuellement une trace de l’état actuel et une liste de tous ( State , Message ) -> Handler . il a également d’autres fonctionnalités. J’essaie de garder cette classe générique et de la sous-classer avec des parameters de type car elle est utilisée plusieurs fois dans mon programme, et à chaque fois avec un ensemble différent de Message / State / Handler . différents StateMachine auront des parameters différents pour leurs gestionnaires.

Approche A

demander à la machine d’état de suivre tous les mappages.

 class StateMachine { static class Delivery { final State state; final Class msg; } HashMap delegateTable; ... } class ServerStateMachine extends StateMachine { ... } 

me permet d’avoir des méthodes de gestionnaire personnalisées pour cette machine à états particulière. les parameters de la méthode handler.process peuvent être remplacés. Cependant, le gestionnaire ne peut pas être paramétré par le type de message.

Problème: cela implique l’utilisation du contrôle d’ instanceof pour chaque gestionnaire de message (s’assurer qu’il reçoit le message qu’il attend).

Approche B

permet de paramétrer chaque gestionnaire de message par type de message

 class MessageHandler { void process(M msg) { .... } } 

Problème: l ‘ effacement de type m’empêchera de les stocker dans un joli hashmap puisque tous les MessageHandler seront typés différemment. si je peux les stocker dans une carte, je ne pourrai pas les récupérer et les appeler avec les arguments appropriés.

Approche C

avoir l’object state gérer tous les messages.

 class State { ... } class ServerState extends State { ... } 

J’ai des gestionnaires de messages liés à des états spécifiques de la machine à états (en les plaçant à l’intérieur) (chaque instance d’une machine à états aurait sa propre liste d’états valides), ce qui permet aux gestionnaires d’être d’un type spécifique. (machine d’état du serveur -> gestionnaire de messages du serveur).

Problème: chaque état ne peut gérer qu’un seul type de message. vous perdez également l’idée que l’état parent peut gérer différents messages que les états enfants. type erasure empêche également StateMachine d’appeler des méthodes de traitement d’états en cours.

Approche D

avoir le processus du message eux-mêmes basé sur l’état.

Problème: jamais vraiment pris en compte, car chaque message devrait avoir un gestionnaire différent basé sur l’état actuel de la machine à états. l’expéditeur ne connaîtra pas l’état actuel de StateMachine .

Approche E

oubliez les génériques et la gestion de l’état / message du code dur avec une instruction switch.

Problème: santé mentale

Solution dangereuse:

Merci pour votre consortingbution tout le monde, je pense que le problème était que je ne l’ai pas réduit à bon problème (trop de discussion) voici ce que j’ai maintenant.

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

Pour l’approche B, n’utilisez pas un hashmap “sympa”. A la place, écrivez des gestionnaires de mappage de conteneurs hétérogènes typesafe dans des objects de classe:

 interface Handler { ...} interface Message {...} interface HandlerContainer {  void register(Class clazz, Handler handler);  Handler getHandler(T t); } class HandlerContainerImpl implements HandlerContainer { private final Map,Handler> handlers = new HashMap,Handler>();  void register(Class clazz, Handler 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")  Handler getHandler(T t) { return (Handler)handlers.get(t.getClass()); } } 

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

C utiliser le modèle de visiteur dans les classes d’état pour répartir le type de message semble être le meilleur choix. Étant donné le choix entre instanceof et Visitor, je pense que Visitor est légèrement plus propre (même s’il est encore maladroit). Les problèmes d’effacement des caractères posent une difficulté notable et le traitement du message semble quelque peu à l’arrière. La notation de machine d’état typique a les états comme centre de contrôle. En outre, vous pouvez demander à la classe abstraite Visiteur pour les types de message de générer une erreur sur tous les états, permettant ainsi aux états d’obtenir gratuitement le retour d’erreur sur les messages non valides.

C + Visitor serait tout à fait analogue à l’approche que j’utilise fréquemment pour implémenter des machines d’état en C ou des langages avec des fonctions de première classe – “state” est représenté par un pointeur sur la fonction gérant les messages dans l’état actuel, cette fonction renvoyant un pointeur sur la fonction d’état suivante (éventuellement elle-même). La boucle de contrôle de la machine à états récupère simplement le message suivant, le transmet à la fonction d’état actuelle et met à jour la notion de “courant” à son retour.

Approche E. Oubliez les génériques et utilisez 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 j’ai vue à quelques endroits consiste à utiliser des annotations. Utilisez des classes POJO régulières et annotez-les pour qu’elles soient traitées par une classe de type de gestionnaire qui exécute la machine à états:

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

Il y a quelques implémentations plus développées, je pense que celle de Scientific Toolbox était bonne mais je ne peux pas trouver le bon lien maintenant: http://mina.apache.org/introduction-to-mina-statemachine.html http: // weblogs. java.net/blog/carcassi/archive/2007/02/finite_state_ma_1.html http://hubris.ucsd.edu/shared/manual.pdf

Approche F:

Oubliez les génériques, sauf si vous avez des modèles spécifiques à un type. Définissez quelques interfaces selon le système souhaité, y compris peut-être quelque chose comme

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

et pour une machine d’état particulière, utilisez des énumérations qui étendent StateMachineState:

 class OtherState { public double x1; public int i; } enum MyState extends StateMachineState { 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 */ } 

(avertissement: code non vérifié pour les erreurs de syntaxe)