我做了一个状态机,希望它能利用java中的泛型。目前,我没有看到我可以让这个工作和获得漂亮的代码的方式。我相信这个设计问题之前已经被处理过很多次了,我正在寻找一些输入。这里有一个粗略的轮廓。
class State { ... }
每个不同的状态对象只有一个副本(主要是绑定到静态最终变量的匿名类),它有每个状态的自定义数据。每个状态对象都有一个状态父对象(有一个根状态)
class Message { ... }
每条消息都是单独创建的,并且每条消息都有自定义数据。它们可能会相互细分。有一个根消息类。
class Handler { ... }
每个处理程序只创建一次,并处理特定的状态/消息组合。
class StateMachine { ... }
当前跟踪当前状态以及所有(State
,Message
) -> Handler
映射的列表。它还具有其他功能。我试图保持这个类的泛型,并在我的程序中多次使用它的类型参数作为它的子类,每次都使用一组不同的Message
/ State
's /和Handler
's。不同的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
让state对象处理所有消息。
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;
}
}
发布于 2009-08-20 07:16:10
对于方法B,不要使用“很好的”hashmap。相反,编写一个将处理程序映射到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-19 16:21:29
方法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-19 16:25:12
E用枚举表示状态和消息可能是最简单的。但它的可扩展性不是很好。
C在状态类中使用Visitor模式来分派消息类型看起来可能是最好的选择。如果要在instanceof
和Visitor之间进行选择,我认为Visitor会稍微干净一些(尽管仍然很尴尬)。类型擦除问题确实构成了一个值得注意的困难,消息中的处理似乎有些倒退。典型的状态机表示法将状态作为控制的中心。此外,您可以让消息类型的Visitor抽象类在所有状态上抛出错误,从而允许状态免费获得无效消息的错误回退。
C+Visitor非常类似于我在用C或具有一流函数的语言实现状态机时经常使用的方法--“状态”由一个指向当前状态中处理消息的函数的指针表示,该函数返回指向下一个状态函数(可能是其自身)的指针。状态机控制循环仅获取下一条消息,将其传递给当前状态函数,并在其返回时更新“当前”的概念。
https://stackoverflow.com/questions/1301007
复制相似问题