Подтвердить что ты не робот

Проблема дизайна дженериков java (конечный автомат)

i создал конечный автомат и хотел бы, чтобы он использовал преимущества дженериков в java. в настоящее время я не вижу, как я могу сделать эту работу и получить довольно перспективный код. Я уверен, что проблема с дизайном уже неоднократно подходила, и я искал некоторые данные. heres грубая схема.

class State { ... }

только одна копия каждого отдельного объекта состояния (в основном анонимные классы, привязанные к статическим конечным переменным), у него есть пользовательские данные для каждого состояния. каждый объект состояния имеет родительский элемент состояния (имеется одно корневое состояние)

class Message { ... } 

каждое сообщение создается отдельно, и каждый из них имеет пользовательские данные. они могут подклассифицировать друг друга. существует один класс сообщений root.

class Handler { ... } 

каждый обработчик создается только один раз и обрабатывает конкретную комбинацию состояний/сообщений.

class StateMachine { ... }

в настоящее время отслеживает текущее состояние и список всех (State, Message) → Handler отображений. он имеет и другие функции. Я пытаюсь сохранить этот класс generic и подклассировать его с параметрами типа, так как он использовал кучу раз в моей программе и каждый раз с другим набором Message/StateHandler. разные 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;
  }

}
4b9b3361

Ответ 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: код не проверен дважды для синтаксических ошибок)