java genéricos problema de projeto (máquina de estado)
-
18-09-2019 - |
Pergunta
i feito uma máquina de estado e gostaria de tirar proveito de genéricos em java. Atualmente eu não vejo a maneira que eu posso fazer este trabalho e obter muito procurando código. im certo de que este problema de projeto foi abordado muitas vezes antes, e estou procurando alguma entrada. heres um esboço.
class State { ... }
apenas uma cópia de cada objeto de estado distinto (aulas na sua maioria anónimos ligados a variáveis ??static final), tem dados personalizado para cada estado. cada objecto de estado tem um pai estado (não é um estado raiz)
class Message { ... }
cada mensagem é criado separadamente e cada um tem dados personalizados. eles podem subclasse outro. há uma classe de mensagem raiz.
class Handler { ... }
cada manipulador é criado apenas uma vez e lida com um determinado estado / mensagem de combinação.
class StateMachine { ... }
atualmente mantém o controle do estado atual, e uma lista de todos (State
, Message
) -> mapeamentos Handler
. ele tem outras funcionalidades bem. Eu estou tentando manter esta classe genérica e subclasse-lo com parâmetros de tipo como seu usado um monte de vezes no meu programa, e cada vez com um conjunto diferente de Message
do / State
de / e Handler
de. diferentes do StateMachine
terá diferentes parâmetros para seus manipuladores.
Aproxime-A
tem estado pista máquina sustento de todos os mapeamentos.
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 ter métodos costume manipulador para este máquina de estado particular. os parâmetros para o método handler.process pode ser substituído. No entanto, o condutor não pode ser parametrizado pelo tipo de mensagem.
Problema:. isso envolve o uso da verificação de sanidade instanceof
para cada manipulador de mensagem (certificando-se de que está recebendo a mensagem que ele está esperando)
Abordagem B
vamos fazer cada manipulador de mensagem parametrizado pelo tipo de mensagem
class MessageHandler<M extends Message> {
void process(M msg) { .... }
}
Problema: Tipo de apagamento me impedirá de armazenar estes em um hashmap bom desde que todos os de MessageHandler
será digitado de forma diferente. se eu posso armazená-los em um mapa, eu não vou ser capaz de recuperá-los e chamá-los com os argumentos apropriados.
Aproxime-C
tem a alça objeto de estado todas as mensagens.
class State<M extends Message> { ... }
class ServerState<M extends ServerMessage> extends State<M> { ... }
i ter manipuladores de mensagens ligadas a estados específicos de máquina de estado (colocando-os dentro), (cada instância de uma máquina de estado teria a sua própria lista de estados válidos), que permite que os manipuladores de ser de um tipo específico. (Máquina de estado do servidor -> mensagem do servidor manipulador).
Problema: cada estado pode apenas lidar com um tipo de mensagem. você também perde a idéia de que o estado pai pode lidar com mensagens diferentes do que estados criança. Tipo de apagamento também impede a StateMachine
de chamar estados atuais processar métodos.
Aproxime-D
tem-se o processo da mensagem com base no estado.
Problema: nunca realmente considerado, uma vez que cada mensagem deve ter um manipulador diferente com base no estado máquina de estado atual. o remetente não vai saber o estado do StateMachine
atual.
Abordagem E
esquecer genéricos e estado código rígido / tratamento de mensagens com uma instrução switch.
Problema: sanidade
inseguro Solução:
Obrigado pelo seu todo mundo entrada, eu acho que o problema era que eu não reduziu isto a bom problema (muito discussão) Aqui está o que eu tenho agora.
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;
}
}
Solução
Para abordagem B, não use um "bom" hashmap. Em vez disso, escrever uma typesafe heterogêneo manipuladores de mapeamento recipiente para objetos 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());
}
}
Outras dicas
E com enums representando estados e mensagens é provavelmente o mais fácil. Mas não é muito extensível.
C usando o padrão Visitor nas classes estaduais para expedição no tipo de mensagem parece que ele pode ser o melhor das suas opções. Dada a escolha entre instanceof
e Visitor, acho Visitor é um pouco mais limpo (embora ainda estranho). Os problemas apagamento tipo fazer representar uma dificuldade notável, e processamento na mensagem parece um pouco para trás. notação típica máquina de estado tem os estados como o centro de controle. Além disso, você pode ter a classe abstrata Visitor para os tipos de mensagens lançar um erro em todos os estados, permitindo estados para obter erro queda-back on mensagens inválidas gratuitamente.
C + Visitor seria bastante análoga à abordagem que eu freqüentemente uso na implementação de máquinas de estado em C ou idiomas com funções de primeira classe - "estado" é representado por um ponteiro para a função de manipulação de mensagens no estado atual, com que a função retornando um ponteiro para a próxima função de estado (possivelmente próprio). A malha de controle de máquina de estado meramente busca a próxima mensagem, passa para a função de estado atual, e atualiza a noção de "atual" quando ele retorna.
Abordagem E. Esqueça os genéricos e interfaces de uso.
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);
}
}
A abordagem que eu tenho visto em alguns lugares, é a utilização de anotações. Use classes POJO regulares e anotá-los para serem processados ??por uma classe tipo de gerente que administra a máquina de estado:
public class MyState {
@OnEntry
public void startStuff() {
...
}
@OnExit()
public void cleanup() {
..
}
}
Existem algumas implementações mais desenvolvidos, eu acho que o Scientific Toolbox era bom, mas eu não posso encontrar o link agora: http://mina.apache.org/introduction-to-mina-statemachine. html http://weblogs.java.net/blog/carcassi/ Arquivo / 2007/02 / finite_state_ma_1.html http://hubris.ucsd.edu/shared/manual.pdf
Abordagem F:
Esqueça os genéricos, a menos que você tem padrões específicos do tipo. Definir um par de interfaces por seu sistema desejado, incluindo, talvez, algo como
interface StateMachineState<R extends StateMachineState,T> {
/* returns next state */
R execute(T otherState);
}
e para uma máquina de estado particular, uso enums que se estendem 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;
}
},
}
Em seguida, você pode fazer 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 */
}
(ressalva: código não verificado duas vezes por erros de sintaxe)