Javaのジェネリック医薬品の設計上の問題(ステートマシン)
-
18-09-2019 - |
質問
私は、ステートマシンを作り、それがJavaでジェネリックを利用したいと思います。現在、私はコードを見て、のかなりの私は、この作品を作ってもらうことができる方法を参照してくださいいけません。イムは確かに、この設計上の問題は前に何度も近づいてきた、とイムは、いくつかの入力を探しています。 HERESにラフアウトラインます。
class State { ... }
各個別の状態オブジェクト(静的最終変数に結び付けほとんど無名クラス)のコピーが1つだけ、それは各状態のためのカスタムデータを持っています。各状態オブジェクトは状態親を有している(1つのルート状態がある)
class Message { ... }
各メッセージは、個別に作成し、それぞれが独自のデータを持っています。彼らはお互いをサブクラス化します。 1つのルートのメッセージクラスがあります。
class Handler { ... }
各ハンドラは一度だけ作成され、特定の状態/メッセージコンボを扱っている。
class StateMachine { ... }
> State
マッピング - は、現在、現在の状態の追跡、およびすべての(Message
、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) { .... }
}
の問題:の型消去は、すべてのMessageHandler
年代が違ったタイプされますので、素敵なハッシュマップでこれらを保存するから私を防ぐことができます。私はマップに保存することができた場合、私はそれらを盗ん、適切arguementsでそれらを呼び出すことができる文句を言わない。
アプローチC
状態オブジェクトは、すべてのメッセージを処理しています。
class State<M extends Message> { ... }
class ServerState<M extends ServerMessage> extends State<M> { ... }
私は、メッセージハンドラは、ハンドラは、特定のタイプのものとすることができるようになります、(ステート・マシンの各インスタンスが有効な状態の独自のリストを持っているでしょう)、(内部にそれらを置くことによって)特定のステートマシンの状態に関連付けられています。 (サーバー・ステート・マシン - >サーバ・メッセージ・ハンドラ)。
の問題:の各状態は、1つのメッセージのみのタイプを処理することができます。あなたはまた、親状態の子の状態とは異なるメッセージを扱うことができるという考えを失います。型消去は、現在の状態のプロセス・メソッドを呼び出すからStateMachine
を防ぎます。
アプローチD
の状態に基づいて、メッセージのプロセスそのものを持っています。
の問題:各メッセージは、現在のステート・マシンの状態に基づいて別のハンドラを持っているはずなので、を決して本当に、と考えます。送信者は、現在のStateMachine
の状態を知ることができません。
アプローチE
ジェネリック医薬品とswitch文で扱うハードコード状態/メッセージを忘れています。
の問題:のの正気の
安全でない解決策:
あなたの入力皆のおかげで、私はこの問題は、私が今持っているHERESに何が良い問題(あまりにも多くの議論)にこれを減少させなかったいたと思います。
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;
}
}
解決
アプローチ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());
}
}
他のヒント
の状態とメッセージを表す列挙型とEは、おそらく最も簡単です。しかし、それは非常に拡張性がありません。
それはあなたのオプションの最高であってもよいようなメッセージタイプにディスパッチする状態のクラスでVisitorパターンを使用して Cが見えます。 instanceof
とビジターの間の選択を考えると、私は(まだぎこちないが)ビジターがわずかにきれいだと思います。型消去の問題が顕著な困難をもたらす行い、およびメッセージの処理がやや後方に思えます。典型的なステートマシンの表記は、制御の中心としての状態を有します。さらに、あなたがメッセージタイプのビジター抽象クラスを持つことができますので、状態は自由のために、無効なメッセージのエラーフォールバックを取得することができ、すべての状態でエラーをスローします。
C +訪問者は、私は頻繁に使用するアプローチに非常に類似するであろう - 「状態」と、現在の状態のメッセージを処理する関数へのポインタで表されますその関数は、次の状態の関数(おそらく自体)へのポインタを返します。ステートマシンの制御ループは、単に現在の状態の関数に渡し、次のメッセージを取り出し、そして「現在」の概念を更新することが戻ったとき。
アプローチ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);
}
}
私はいくつかの場所で見てきたアプローチは、注釈を使用することです。通常のPOJOクラスを使用してステートマシンを実行マネージャ型クラスによって処理されるように、それらに注釈を付けます:
public class MyState {
@OnEntry
public void startStuff() {
...
}
@OnExit()
public void cleanup() {
..
}
}
さらにいくつかの先進の実装がありますが、私は科学的なツールボックス1が良かったと思いますが、私は今、右のリンクを見つけるカント: http://mina.apache.org/introduction-to-mina-statemachine。 HTML の http://weblogs.java.net/blog/carcassi/アーカイブ/ 2007/02 / finite_state_ma_1.htmlする http://hubris.ucsd.edu/shared/manual.pdfする
アプローチ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 */
}
(注意点:コードではない構文エラーをダブルチェック)