私のJavaゲームサーバーでは、基本的にパケットの目的をサーバーに伝えるパケットのアクションIDを送信します。各アクション ID (整数) を関数にマップしたいと考えています。スイッチを使わずにこれを行う方法はありますか?
10 に答える
これはどうですか?
HashMap<Integer, Runnable> map = new HashMap<Integer, Runnable>();
map.put(Register.ID, new Runnable() {
public void run() { functionA(); }
});
map.put(NotifyMessage.ID, new Runnable() {
public void run() { functionB(); }
});
// ...
map.get(id).run();
(いくつかの引数を渡す必要がある場合は、適切なパラメーターを持つ関数を使用して独自のインターフェイスを定義し、Runnable の代わりにそれを使用します)。
別の同様のアプローチは、Java 8 のサプライヤーを使用することです。
Map<Integer, Supplier<T>> suppliers = new HashMap();
suppliers.put(1, () -> methodOne());
suppliers.put(2, () -> methodTwo());
// ...
public T methodOne() { ... }
public T methodTwo() { ... }
// ...
T obj = suppliers.get(id).run();
Java には、ファーストクラスの関数ポインターがありません。同様の機能を実現するには、インターフェースを定義して実装する必要があります。匿名の内部クラスを使用して簡単にすることはできますが、それでもあまりきれいではありません。次に例を示します。
public interface PacketProcessor
{
public void processPacket(Packet packet);
}
...
PacketProcessor doThing1 = new PacketProcessor()
{
public void processPacket(Packet packet)
{
// do thing 1
}
};
// etc.
// Now doThing1, doThing2 can be used like function pointers for a function taking a
// Packet and returning void
Java には実際には関数ポインターがありません (代わりに匿名の内部クラスがあります)。ただし、タイプではなく値をオンにしている限り、スイッチを使用しても問題はありません。スイッチを使いたくない理由はありますか?コードのどこかでアクション ID とアクションの間のマッピングを行う必要があるように思われるので、単純にしてみませんか?
ええ、しかしインターフェイスを使用すると、コールバックごとにインターフェイスを作成する必要があります。これは、セットに渡したいすべての関数を意味します。これを処理するデリゲート クラスを作成すると、(真の関数ポインターではなく) 渡される関数が得られます。また、ジェネリックを乱用して戻り値の型にする場合、キャストする必要がないため、ボトルネックがほとんどなくなります。
C# デリゲート (正確には MultiCastDelegate) は、メソッド MethodInfo を使用して情報を取得します。これは、java.lang.reflect.method を使用してデリゲート クラスに対して行う必要があるのと同じことです。Delegate(T) クラスのコードをこのサイトの別のフォームに投稿し、この問題を扱っています。(はい)C++からであるため、関数(特にVoid)を渡すためのより良い方法が必要であり、関数以上のインターフェイスを作成する必要があるためです。これで、パラメーター情報を入力する関数を選択できるようになりました。出来上がり!JIT や JVM による速度の低下がなく、使いやすく便利です。そして、Java プログラミングを 1 週間しか学べなかったとしたら、Java プログラマーなら誰でもそれを行うことができます。
また、基本リスナーと基本インターフェースを作成してリスナーに渡す場合にも非常に役立ちます。関数の名前が変更されたため、別のリスナーを作成する必要がなくなりました。デリゲート クラスを作成することには、非常に使いやすく、まずまずという大きな利点があります。
Swing/AWT を使用したことがありますか? 彼らのイベント階層は、同様の問題を解決します。Javaが関数を渡す方法は、インターフェースを使用します。たとえば、
public interface ActionHandler {
public void actionPerformed(ActionArgs e);
}
次に、整数をこれらのオブジェクトにマップしたい場合は、 a のようなものを使用してそれjava.util.HashMap<Integer,ActionHandler>
を管理できます。実際の実装は、匿名クラス (Java の "ラムダ" の最適な近似) またはどこかの適切なクラスのいずれかになります。匿名クラスの方法は次のとおりです。
HashMap<Integer,ActionHandler> handlers;
handlers.put(ACTION_FROB, new ActionHandler() {
public void actionPerformed(ActionArgs e) {
// Do stuff
// Note that any outer variables you intend to close over must be final.
}
});
handlers.get(ACTION_FROB).actionPerformed(foo);
(編集)さらに虐待したい場合は、次のように HashMap を初期化できます。
HashMap<Integer,String> m = new HashMap<Integer,String>() {{
put(0,"hello");
put(1,"world");
}};
静的メソッドをインターフェースできます。このメソッドでは、パラメーターも指定できます。インターフェイスを宣言します...
public interface RouteHandler {
void handleRequest(HttpExchange t) throws IOException;
}
そしてあなたの地図...
private Map<String, RouteHandler> routes = new HashMap<>();
次に、インターフェイス/パラメーターに一致する静的メソッドを実装します...
public static void notFound(HttpExchange t) throws IOException {
String response = "Not Found";
t.sendResponseHeaders(404, response.length());
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
その後、それらのメソッドをマップに追加できます...
routes.put("/foo", CoreRoutes::notFound);
そしてそれらを次のように呼び出します...
RouteHandler handler = routes.get("/foo");
handler.handleRequest(exchange);
これは、責任の連鎖パターンを使用して行うことができます。
これは、リンクリストのように、さまざまなオブジェクトをリンクするパターンです。つまり、各オブジェクトには、チェーン内の次への参照があります。チェーン内のオブジェクトは通常、1つの特定の動作を処理します。オブジェクト間のフローは、switch-caseステートメントと非常によく似ています。
ロジックが分散したり、チェーンが長すぎるとパフォーマンスの問題が発生したりするなど、いくつかの落とし穴があります。しかし、これらの落とし穴に加えて、テスト容易性が向上し、結束力が強化されるという利点があります。また、分岐のトリガーとしてenum、byte、int short、およびchar式を使用することに限定されません。
lambdaj ライブラリでクロージャがどのように実装されているかを確認してください。実際には、C# デリゲートと非常によく似た動作をします。