3

オブザーバー パターン スタイルのソリューションを使用して、さまざまな種類のメッセージを送信するためのクリーンなソリューションの設計に問題があります。

TCP ソケットを介して (変更できない) サーバーに接続しているクライアント アプリケーションがあります。メッセージのタイプを定義する「msg」パラメーターを常に含むjsonエンコードされたメッセージを送受信できます。また、複数のクライアントに送信され、自分のクライアントから要求されていないメッセージを受信できることにも注意してください (たとえば、誰かがチャット メッセージを送信した場合)。

例: 接続時に受信{"msg":"ServerInfo","version":"1.0a"}

{"msg":"Ping"}で返信を送信する{"msg":"Ping","time":1381358623}

{"msg":"Chat", "from":"Person", "text":"Hello everyone"}いつでも受け取れました

一部のメッセージはより複雑で、ネストされたオブジェクトを持つことができます。たとえば、

{
    "msg":"SampleData",
    "people":[{
        "name":"Joe"
        "age":25
    },{
        "name":"Bob",
        "age":30
    }]
}

メッセージには数十の異なる種類があり、すべてさまざまな量と種類のフィールドがあります。

私は現在、ソケットをリッスンし、Gson を使用してすべてのメッセージを "msg" パラメータのみを持つ "BasicMessage" クラスに解析するクラスを持っています。すべてのメッセージ タイプ文字列をそれぞれのクラスにマッピングする Map があります。「msg」パラメーターを取得したら、Gson を使用して逆シリアル化する必要があるクラスを検索し、そうすることができます。これで正しいクラスのインスタンスができましたが、ここから設計が崩壊し始めます。

他のさまざまなクラスに、数種類のメッセージのみをサブスクライブできる機能を持たせたいと考えています。問題は、多くの instanceof を必要とするか、すべてのクライアントですべてを再解析することなく、これを行う方法を見つけることができないように見えることです。

私の最初の考えは、次のようにパラメーター化されたインターフェースを使用することでした。

public interface MessageListener<T> {
    public void onReceivedMessage(T message);
}

次に、メッセージを逆シリアル化するクラスで、List<MessageListener<Message>>Message が継承された他のすべての Message の抽象クラスである場所がありました。MessageListener<SpecificMessage>次に、継承元ではない型消去の問題に遭遇したMessageListener<Message>ため、クライアントを 1 つの単純なリストに追加する方法がありませんでした。メッセージの種類ごとにリストを作成する必要があるように思えましたが、これも理想的ではありません。この設計のもう 1 つの問題は、別のクラスでパラメーター化したとしても、同じインターフェイスを 2 回実装することはできないため、複数のメッセージ リスナーが必要なクラスのさまざまなメッセージ リスナーに対してのみ匿名の内部クラスを使用するように制限されることです。 .

この状況で使用できるより良いパターンはありますか? これを「きちんと」機能させるには、リフレクションを使用する必要があるように感じます。理想的には、別のメッセージ タイプを追加する場合は、デシリアライズするクラスを追加するだけで済み、メッセージ文字列からそのクラスへのマッピングが必要になるだけで、そのタイプのメッセージのリスナーを追加できるようになります。別のクラス。

前もって感謝します!

4

3 に答える 3

1

guavaイベントバスは、従来のObserverパターンの実装を簡素化するように設計されたパブリッシュ/サブスクライブ メッセージング コンポーネントを提供します。専用ListenerまたはObserverインスタンスをイベント発行オブジェクトに明示的に登録する代わりに、特定のイベントに関心のあるクラスをバスに登録します。

class MyEventListeningClass {
   @Subscribe public void onEvent(MyEvent e) {
      // react to event
   }
}
...
eventBus.register(new MyEventListeningClass());

関心のあるサブスクライバーへのイベントの登録とディスパッチは、リフレクションによって行われます。

質問やその他の回答で既に見たように、Java の型システムでは、リスナー インターフェイスで同様の柔軟性を実現することが困難です。問題のユース ケースでは、イベント バスが適しているように思われます。特に、将来さらに多くのメッセージ タイプを追加する必要がある場合はそうです。残念ながら、このような疎結合の通常の警告が適用されます。メッセージで正確に何が起こるかを推測するのは困難です。

于 2013-10-10T08:21:39.277 に答える
0

ここに可能性があります(ただし、テストしていないことに注意してください)。残念ながら、のすべてのサブクラスに特定のメソッドを追加する必要がありBasicMessageます。重複を制限するために、できるだけ短くしようとしました。各クラスSpecificMessageで、これを追加します。

public static void addListener (SocketListener s, MessageListener<SpecificMessage>               listener)
{
    s.addSubclassListener (SpecificMessage.class, listener);
}

私はSocketListener、ソケットをリッスンし、後でサブスクライバーにメッセージを送信するクラスであると想定しています。(適切なものに変更できます。)

SocketListener

private interface BasicMessageListener {
    public void onReceivedMessage (BasicMessage message);
}

protected <T extends BasicMessage> void addSubclassListener (final Class<?> clazz, final MessageListener<T> listener)
{
    addBasicMessageListener (clazz, new BasicMessageListener () {
        public void onReceivedMessage (BasicMessage message) {
            if (message.getClass() != clazz)
                throw new ClassCastException ();
            listener.onReceivedMessage ((T) message);
        }
    }
}

addBasicMessageListenerクラスのリスナーを登録するメソッドになります。そのパラメーターは一般的ではないため、リストに追加できるはずです。(注: への型キャスト(T)は、チェックされていない警告を表示します。) アイデアは、他のクラスが class のメッセージをリッスンするリスナーを登録しSpecificMessageXYZたい場合、それらは使用するということです。

SpecificMessageXYZ.addListener (theSocketListener, new MessageListener<SpecificMessageXYZ> () {
    ...
    ... a listener that takes a SpecificMessageXYZ parameter
    ...
});

またはその趣旨の何か。これには依然としてダウンキャストが含まれますが、他のクラスが登録する可能性のあるすべてのリスナーで and ダウンキャストaddSubclassListenerを実行する必要はなく、( の) 1 か所でのみ行われます。instanceof複製されたメソッドをすべてのメッセージ クラスに配置しなければならないことを補うのに、それで十分かどうかはわかりません。しかし、リフレクションを使わずに別の方法を考えることはできません。あなたが探しているのは、Java には存在しない興味深い種類の共分散だと思います。

繰り返しますが、これを実際に試したことはありませんが、違法なことをしていないことを確認するためにコンパイラを実行したことを除きます。また、メソッド/クラス名の選択がお粗末になる傾向があることに注意してください。

編集:(T) messageチェックを実行することはできませんが、チェックを実行するために必要な情報が既にあるので、そうする必要があります。

于 2013-10-10T00:47:52.227 に答える