java泛型设计问题(状态机)

时间:2009-08-19 16:09:48

标签: java design-patterns generics state-machine

我制作了一台状态机,并希望它能利用java中的泛型。目前我没有看到我可以使这个工作的方式,并获得漂亮看代码。我确定此设计问题已经多次接近,我正在寻找一些输入。这是一个粗略的轮廓。

class State { ... }

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

class Message { ... } 

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

class Handler { ... } 

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

class StateMachine { ... }

目前跟踪当前状态,并列出所有(StateMessage) - > Handler映射。它还有其他功能。我试图保持这个类通用,并使用类型参数对其进行子类化,因为它在我的程序中使用了很多次,并且每次使用不同的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) { .... }
}

问题:类型擦除会阻止我将这些存储在一个漂亮的hashmap中,因为所有MessageHandler的输入都会有所不同。如果我可以将它们存储在地图中,我将无法撤回它们并用适当的论证来称呼它们。

方法C

让状态对象处理所有消息。

class State<M extends Message> { ... }
class ServerState<M extends ServerMessage> extends State<M> { ... }

我将消息处理程序绑定到特定的状态机状态(通过将它们放入其中),(状态机的每个实例都有自己的有效状态列表),这允许处理程序具有特定类型。 (服务器状态机 - &gt;服务器消息处理程序)。

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

}

5 个答案:

答案 0 :(得分: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);
  }
}

答案 1 :(得分:2)

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

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

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

答案 2 :(得分: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

答案 3 :(得分:1)

对于方法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());

    }

}

答案 4 :(得分:-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 */        
}

(警告:代码未经双重检查语法错误)