17

ステートマシンを作成し、Javaのジェネリックを利用したいと考えています。現在、私はこれを機能させて見栄えの良いコードを取得する方法がわかりませ。この設計の問題は以前にも何度もアプローチされていると確信しており、いくつかの入力を探しています。ここに大まかな概要があります。

class State { ... }

各個別の状態オブジェクト(ほとんどの場合、静的最終変数に関連付けられた匿名クラス)のコピーが1つだけあり、各状態のカスタムデータがあります。各状態オブジェクトには状態の親があります(ルート状態は1つです)

class Message { ... } 

各メッセージは個別に作成され、各メッセージにはカスタムデータがあります。それらは互いにサブクラス化する場合があります。ルートメッセージクラスが1つあります。

class Handler { ... } 

各ハンドラーは1回だけ作成され、特定の状態/メッセージの組み合わせを処理します。

class StateMachine { ... }

State現在、現在の状態と、すべての( 、Message)->Handlerマッピングのリストを追跡します。他の機能もあります。私はこのクラスをジェネリックに保ち、プログラムで何度も使用され、毎回異なるセットのMessage's / 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

状態オブジェクトにすべてのメッセージを処理させます。

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

私はメッセージハンドラーを特定のステートマシンの状態に(それらを内部に置くことによって)結び付けています(ステートマシンの各インスタンスには有効な状態の独自のリストがあります)。これにより、ハンドラーを特定のタイプにすることができます。(サーバーステートマシン->サーバーメッセージハンドラー)。

問題:各状態は1つのメッセージタイプしか処理できません。また、親の状態が子の状態とは異なるメッセージを処理できるという考えも失われます。型消去は、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がおそらく最も簡単です。しかし、それはあまり拡張可能ではありません。

状態クラスのVisitorパターンを使用してメッセージタイプをディスパッチするCは、最適なオプションのようです。とVisitorのどちらかを選択するinstanceofと、Visitorの方が少しクリーンだと思います(まだ厄介ですが)。型消去の問題は顕著な問題を引き起こし、メッセージの処理はやや逆行しているように見えます。典型的なステートマシン表記法は、制御の中心として状態を持っています。さらに、メッセージタイプのVisitor抽象クラスにすべての状態でエラーをスローさせることができるため、状態は無効なメッセージでエラーフォールバックを無料で取得できます。

C + Visitorは、Cまたはファーストクラス関数を使用する言語でステートマシンを実装するときによく使用するアプローチに非常に似ています。「state」は、現在の状態でメッセージを処理する関数へのポインタで表され、その関数は次の状態関数(おそらくそれ自体)へのポインター。ステートマシン制御ループは、次のメッセージをフェッチし、それを現在の状態関数に渡し、メッセージが戻ったときに「現在」の概念を更新するだけです。

于 2009-08-19T16:25:12.200 に答える
1

私がいくつかの場所で見たアプローチは、注釈を使用することです。通常のPOJOクラスを使用し、ステートマシンを実行するマネージャータイプクラスによって処理されるように注釈を付けます。

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

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

さらに開発された実装がいくつかあります。ScientificToolboxの実装は良かったと思いますが、現在正しいリンクを見つけることができません: 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

アプローチ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

アプローチ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 に答える