Problema de diseño de genéricos de java(máquina de estado)
design-patterns generics (5)
Hice una máquina de estados y me gustaría aprovechar los genéricos de Java. Actualmente no veo la forma en que puedo hacer que esto funcione y obtener un código bonito . Estoy seguro de que este problema de diseño se ha abordado muchas veces antes, y estoy buscando alguna información. Heres un esquema aproximado.
class State { ... }
solo una copia de cada objeto de estado distinto (en su mayoría clases anónimas vinculadas a variables finales estáticas), tiene datos personalizados para cada estado. cada objeto de estado tiene un estado principal (hay un estado raíz)
class Message { ... }
Cada mensaje se crea por separado y cada uno tiene datos personalizados. pueden ser subclases hay una clase de mensaje raíz
class Handler { ... }
cada controlador se crea una sola vez y se ocupa de un combo de estado / mensaje específico.
class StateMachine { ... }
actualmente realiza un seguimiento del estado actual, y una lista de todas las asignaciones ( State
, Message
) -> Controladores. Tiene otra funcionalidad también. Estoy tratando de mantener esta clase genérica y la subclase con parámetros de tipo como su uso un montón de veces en mi programa, y cada vez con un conjunto diferente de Message
''s / State
'' s / and Handler
''s. Los diferentes StateMachine
tendrán diferentes parámetros para sus manejadores.
Enfoque A
Haga que la máquina de estado realice un seguimiento de todas las asignaciones.
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 permite tener métodos de manejo personalizados para esta máquina de estado en particular. Los parámetros para el método handler.process se pueden sobrescribir. Sin embargo, el controlador no puede ser parametrizado por el tipo de mensaje.
Problema: esto implica el uso de la instanceof
verificación de validez para cada controlador de mensajes (asegurándose de que esté recibiendo el mensaje que está esperando).
Enfoque B
permite que cada manejador de mensajes esté parametrizado por tipo de mensaje
class MessageHandler<M extends Message> {
void process(M msg) { .... }
}
Problema: el borrado de tipo evitará que los almacene en un buen hashmap ya que todos los MessageHandler
se escribirán de manera diferente. si puedo almacenarlos en un mapa, no podré recuperarlos y llamarlos con los argumentos adecuados.
Enfoque C
tener el objeto de estado manejar todos los mensajes.
class State<M extends Message> { ... }
class ServerState<M extends ServerMessage> extends State<M> { ... }
Tengo controladores de mensajes vinculados a estados de máquinas de estados específicos (al ponerlos dentro), (cada instancia de una máquina de estados tendría su propia lista de estados válidos), lo que permite que los controladores sean de un tipo específico. (máquina de estado del servidor -> controlador de mensajes del servidor).
Problema: cada estado solo puede manejar un tipo de mensaje. también pierde la idea de que el estado principal puede manejar mensajes diferentes a los estados secundarios. el borrado de tipo también evita que StateMachine
llame a los métodos de proceso de los estados actuales.
Enfoque d
tener el proceso del mensaje basado en el estado.
Problema: nunca se tuvo en cuenta, ya que cada mensaje debe tener un controlador diferente según el estado actual de la máquina de estado. El remitente no sabrá el estado actual de StateMachine
.
Enfoque E
olvídese de los genéricos y del estado del código duro / manejo de mensajes con una instrucción de cambio.
Problema: la cordura
Solución insegura:
Gracias por su aporte a todos, creo que el problema fue que no lo reduje a un buen problema (demasiada discusión) aquí está lo que tengo ahora.
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;
}
}
E con enumeraciones que representan estados y mensajes es probablemente el más fácil. Pero no es muy extensible.
C El uso del patrón de visitante en las clases estatales para enviar el tipo de mensaje parece que puede ser la mejor de sus opciones. Dada la elección entre instanceof
y Visitor, creo que Visitor está un poco más limpio (aunque todavía incómodo). Los problemas de borrado de tipo plantean una dificultad notable, y el procesamiento del mensaje parece algo al revés. La notación de máquina de estado típica tiene los estados como centro de control. Además, puede hacer que la clase abstracta de Visitante para los tipos de mensajes arroje un error en todos los estados, lo que permite que los estados obtengan un error en los mensajes no válidos de forma gratuita.
C + Visitor sería bastante análogo al enfoque que utilizo con frecuencia al implementar máquinas de estado en C o lenguajes con funciones de primera clase: "estado" se representa mediante un puntero a la función que maneja los mensajes en el estado actual, con esa función regresando un puntero a la siguiente función de estado (posiblemente sí). El bucle de control de la máquina de estado simplemente captura el mensaje siguiente, lo pasa a la función de estado actual y actualiza la noción de "actual" cuando regresa.
El enfoque que he visto en algunos lugares, es utilizar anotaciones. Use las clases regulares de POJO y anótelas para ser procesadas por una clase de tipo administrador que ejecuta la máquina de estado:
public class MyState {
@OnEntry
public void startStuff() {
...
}
@OnExit()
public void cleanup() {
..
}
}
Hay algunas implementaciones más desarrolladas, creo que la caja de herramientas científicas era buena, pero no puedo encontrar el enlace correcto ahora: 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
Enfoque E. Olvídate de los genéricos y usa 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);
}
}
Enfoque F:
Olvídate de los genéricos a menos que tengas patrones específicos de tipo. Defina un par de interfaces para su sistema deseado, incluyendo tal vez algo como
interface StateMachineState<R extends StateMachineState,T> {
/* returns next state */
R execute(T otherState);
}
y para una máquina de estado particular, use enumeraciones que extiendan 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;
}
},
}
Entonces puedes hacer algo como:
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 */
}
(advertencia: el código no se verifica dos veces para detectar errores de sintaxis)
Para el enfoque B, no utilice un hashmap "bonito". En su lugar, escriba un contenedor heterogéneo para el manejo de tipos en los objetos de clase:
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());
}
}