Проблема дизайна дженериков java (конечный автомат)
i создал конечный автомат и хотел бы, чтобы он использовал преимущества дженериков в java. в настоящее время я не вижу, как я могу сделать эту работу и получить довольно перспективный код. Я уверен, что проблема с дизайном уже неоднократно подходила, и я искал некоторые данные. heres грубая схема.
class State { ... }
только одна копия каждого отдельного объекта состояния (в основном анонимные классы, привязанные к статическим конечным переменным), у него есть пользовательские данные для каждого состояния. каждый объект состояния имеет родительский элемент состояния (имеется одно корневое состояние)
class Message { ... }
каждое сообщение создается отдельно, и каждый из них имеет пользовательские данные. они могут подклассифицировать друг друга. существует один класс сообщений root.
class Handler { ... }
каждый обработчик создается только один раз и обрабатывает конкретную комбинацию состояний/сообщений.
class StateMachine { ... }
в настоящее время отслеживает текущее состояние и список всех (State
, Message
) → Handler
отображений. он имеет и другие функции. Я пытаюсь сохранить этот класс generic и подклассировать его с параметрами типа, так как он использовал кучу раз в моей программе и каждый раз с другим набором Message
/State
/и Handler
. разные StateMachine
будут иметь разные параметры для своих обработчиков.
Подход A
имеет автоответчик для отслеживания всех сопоставлений.
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> {
...
}
позволяет мне иметь настраиваемые методы обработчика для этого конкретного конечного автомата. параметры метода handler.process могут быть перезаписаны. Однако обработчик не может быть параметризован типом сообщения.
Проблема: это включает в себя проверку работоспособности instanceof
для каждого обработчика сообщений (убедитесь, что он получает ожидаемое сообщение).
Подход B
позволяет сделать каждый обработчик сообщения параметризованным по типу сообщения
class MessageHandler<M extends Message> {
void process(M msg) { .... }
}
Проблема: стирание типа не позволит мне хранить их в хорошем хешмапе, так как все MessageHandler
будут введены по-разному. если я могу хранить их на карте, я не смогу их отыскать и называть их соответствующими аргументами.
Подход C
объект объекта обрабатывает все сообщения.
class State<M extends Message> { ... }
class ServerState<M extends ServerMessage> extends State<M> { ... }
У меня есть обработчики сообщений, привязанные к конкретным состояниям состояния машины (путем помещения их внутрь), (каждый экземпляр конечного автомата будет иметь свой собственный список допустимых состояний), что позволяет обработчикам быть определенного типа. (серверный конечный автомат → обработчик сообщений сервера).
Проблема: каждое состояние может обрабатывать только один тип сообщения. вы также теряете идею о том, что родительское состояние может обрабатывать разные сообщения, чем дочерние состояния. type erasure также предотвращает вызов StateMachine
методов текущих состояний.
Подход D
сам процесс сообщения основывается на состоянии.
Проблема: никогда не рассматривалась, так как каждое сообщение должно иметь другой обработчик, основанный на текущем состоянии конечного автомата. отправитель не будет знать текущее состояние StateMachine
.
Подход E
забыть о дженериках и состоянии жесткого кода/обработке сообщений с помощью оператора switch.
Проблема: здравомыслие
Небезопасное решение:
Спасибо за ваш вклад всех, я думаю, проблема в том, что я не уменьшил это до хорошей проблемы (слишком много обсуждения), что у меня есть сейчас.
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;
}
}
Ответы
Ответ 1
Для подхода B не используйте "хороший" хэш файл. Вместо этого напишите гетерогенные обработчики сопоставления контейнеров типов в объекты класса:
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());
}
}
Ответ 2
Подход E. Забудьте о дженериках и используйте интерфейсы.
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);
}
}
Ответ 3
E с перечислениями, представляющими состояния и сообщения, вероятно, является самым простым. Но это не очень расширяемо.
C, используя шаблон посетителя в классах состояний для отправки по типу сообщения, похоже, что это может быть лучшим из ваших вариантов. Учитывая выбор между instanceof
и посетителем, я думаю, что посетитель немного чище (хотя все еще неловко). Проблемы с стиранием типа представляют значительную трудность, и обработка в сообщении кажется несколько обратным. Типичное обозначение конечного устройства имеет состояния как центр управления. Кроме того, у вас может быть абстрактный класс Visitor для типов сообщений, которые вызывают ошибку во всех состояниях, что позволяет государствам бесплатно получать ошибки в недопустимых сообщениях.
C + Visitor будет совершенно аналогичен подходу, который я часто использую при реализации государственных машин на C или языках с первоклассными функциями. "Состояние" представлено указателем на сообщения обработки функций в текущем состоянии, с эта функция возвращает указатель на следующую функцию состояния (возможно, сама). Контур управления конечным автоматом просто извлекает следующее сообщение, передает его текущему состоянию и обновляет понятие "текущий", когда он возвращается.
Ответ 4
Подход, который я видел в нескольких местах, заключается в использовании аннотаций. Используйте обычные классы POJO и аннотируйте их для обработки классом типа менеджера, который запускает конечный автомат:
public class MyState {
@OnEntry
public void startStuff() {
...
}
@OnExit()
public void cleanup() {
..
}
}
Есть еще несколько разработанных реализаций, я думаю, что Scientific Toolbox был хорош, но я не могу найти правильную ссылку сейчас:
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
Ответ 5
Подход F:
Забудьте о дженериках, если у вас нет шаблонов типа. Определите пару интерфейсов в вашей желаемой системе, включая, возможно, что-то вроде
interface StateMachineState<R extends StateMachineState,T> {
/* returns next state */
R execute(T otherState);
}
и для конкретной машины состояний используйте перечисления, которые расширяют 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;
}
},
}
Затем вы можете сделать что-то вроде:
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 */
}
(caveat: код не проверен дважды для синтаксических ошибок)