17

我制作了一个状态机,并希望它能够利用 Java 中的泛型。目前我没有看到我可以使这项工作并获得漂亮的代码的方式。我确信这个设计问题之前已经被处理过很多次,我正在寻找一些输入。这是一个粗略的轮廓。

class State { ... }

每个不同的状态对象(主要是与静态最终变量绑定的匿名类)只有一个副本,它具有每个状态的自定义数据。每个状态对象都有一个状态父级(有一个根状态)

class Message { ... } 

每条消息都是单独创建的,并且每条消息都有自定义数据。它们可以相互子类化。有一个根消息类。

class Handler { ... } 

每个处理程序仅创建一次并处理特定的状态/消息组合。

class StateMachine { ... }

当前跟踪当前状态,以及所有 ( State, Message) ->Handler映射的列表。它还具有其他功能。我试图保持这个类的通用性并使用类型参数对其进行子类化,因为它在我的程序中使用了很多次,并且每次都使用一组不同的Message's / State's / 和Handler's。不同StateMachine的 's 将对其处理程序具有不同的参数。

方法 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> { ... }

我有与特定状态机状态相关的消息处理程序(通过将它们放入内部),(状态机的每个实例都有自己的有效状态列表),这允许处理程序属于特定类型。(服务器状态机 -> 服务器消息处理程序)。

问题:每个状态只能处理一种消息类型。您也失去了父状态可以处理与子状态不同的消息的想法。类型擦除还可以防止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;
  }

}
4

5 回答 5

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);
  }
}
于 2009-08-19T16:21:28.560 回答
2

带有表示状态和消息的枚举的 E 可能是最简单的。但它不是很可扩展。

C 在状态类中使用访问者模式来发送消息类型看起来可能是您最好的选择。如果可以在instanceof和访问者之间进行选择,我认为访问者稍微干净一些(尽管仍然很尴尬)。类型擦除问题确实带来了显着的困难,并且消息中的处理似乎有些倒退。典型的状态机符号以状态为控制中心。此外,您可以让消息类型的访问者抽象类在所有状态上抛出错误,从而允许状态免费获得无效消息的错误回退。

C+Visitor 与我在 C 或具有一流函数的语言中实现状态机时经常使用的方法非常相似——“状态”由指向当前状态下处理消息的函数的指针表示,该函数返回指向下一个状态函数(可能是它自己)的指针。状态机控制循环仅获取下一条消息,将其传递给当前状态函数,并在返回时更新“当前”的概念。

于 2009-08-19T16:25:12.200 回答
1

对于方法 B,不要使用“漂亮”的哈希图。相反,编写一个异构类型安全容器映射处理程序到 Class 对象:

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());

    }

}
于 2009-08-20T07:16:09.990 回答
1

我在几个地方看到的方法是使用注释。使用常规 POJO 类并注释它们以由运行状态机的管理器类型类处理:

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

 @OnExit() 
 public void cleanup() {
  ..
 } 
}

还有一些更发达的实现,我认为科学工具箱很好,但我现在找不到正确的链接: 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

于 2009-08-19T16:55:31.390 回答
-1

方法 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 */        
}

(警告:代码未仔细检查语法错误)

于 2009-08-19T16:29:55.063 回答