2

メッセージをハンドラーにルーティングするクラスがあるとしましょう。このクラスは、ソケットを介してメッセージを取得する別のクラスからメッセージを取得しています。したがって、ソケットはある種のメッセージを含むバッファを取得します。

メッセージをルーティングするクラスは、メッセージタイプを認識しています。すべてのメッセージは、メッセージIDを含むMessageクラスを継承しており、もちろん、独自のパラメーターを追加します。

問題は、どのようにしてメッセージをバッファから転送して、正しいタイプの実際のメッセージインスタンスにすることができるかということです。

たとえば、Messageを継承するDoSomethingMessageがあります。メッセージを含むバッファーを取得しましたが、それがDoSomethingMessageであることを実際に知らずに、何らかの方法でバッファーをDoSomethingMessageに戻す必要があります。

バッファをMessageRouterに転送し、そこでIDでチェックして適切なインスタンスを作成することもできますが、それは私には本当に悪い設計のように思えます。

助言がありますか?

4

4 に答える 4

0

ソケットを介してメッセージを渡す場合は、渡すメッセージの種類を識別するタグを渡す必要があります。このようにして、ソケットからデータを読み取るときに、作成する必要のあるオブジェクトのタイプがわかります。コードは、作成する必要のあるメッセージの種類を知っている必要があります。ソケットからのバイナリブロブには、それが何であるかについての情報は含まれていません。

于 2010-01-22T20:10:30.757 に答える
0

そもそもデータが何を表現しようとしているのかわからない場合、どのようにしてデータを論理表現に変換できますか?私があなたに送る場合、私0x2FD483EBがそれで何を表現しようとしているのかを知らない限り、あなたがそれが何を意味するのかを知る方法はありません-多分それは単一の32ビット数、多分16ビット数のペア、多分48の文字列ですビット文字。

ソケットから生データを取得しているため、ポリモーフィズムに使用されるコンパイラの魔法に頼ることはできません。IDを読み取り、古き良きを使用して適切なクラスを作成するだけですswitch。もちろん、これをオブジェクト指向の優れたレイヤーでラップして、子クラスが自分のIDとファクトリクラスを認識して適切なクラスを作成できるようにすることもできます。

于 2010-01-22T20:11:04.420 に答える
0

メッセージの逆シリアル化を抽象化できます。最初はオブジェクトへのバッファを持っているだけの「MessageHolder」クラスがあります。それは方法を持っているでしょう:

IMessageInterface NarrowToInterface(MessageId id);

あなたのルーターがそれがどんなタイプのメッセージであるかをすでに知っているかどうかは私にはわかりませんでした。その場合、メッセージホルダーインスタンスを受け取り、そのインスタンスでNarrowToInterfaceメソッドを呼び出します。

適切なタイプのIDを渡します。ルーターがタイプを認識していない場合は、MessageHolderオブジェクトにもプロパティがあります。

MessageId GetMessageType();

ルーターは、ルーティング先を決定するためにどのメッセージタイプであるかを学習するために使用します。それがどのように実装されるかについては後で詳しく説明します。

IMessageInterfaceは、メッセージの受信者がどのタイプを予期するかを知っているため、適切なタイプにダウンキャストする抽象クラスまたはインターフェイスです。さまざまなメッセージがすべてよく知られており、ジェネリックスまたはテンプレートを使用できる場合は、NarrowToInterfaceメソッドを、戻り値をテンプレートパラメーターとして受け取るテンプレートメソッドにすることができます。これにより、型の安全性が向上します。テンプレートがない場合は、「Vistor」パターンのダブルディスパッチ手法を使用できます。詳細については、Googleの「ダブルディスパッチビジター」をご覧ください。

メッセージのタイプが明確に定義されていないか、将来的に大きくなる可能性がある場合は、ある時点で(コンパイラーで検証できない)ダウンキャストを処理する必要があります。私が提案している実装は、これを可能な限りカプセル化し、私が知る限り、結合をその絶対最小値に制限します。

また、これを機能させるには、メッセージをヘッダーの標準識別子でフレーム化する必要があります。つまり、メッセージ全体の長さとメッセージタイプのIDを持つ標準ヘッダーがあります。このようにして、ソケットエンドポイントはメッセージの基本を解析し、メッセージホルダーに入れることができます。MessageHolderは、NarrowToInterface()メソッドを実装するために、すべての異なるメッセージタイプ自体を知ることができます。または、メッセージタイプごとにNarrowToInterfaceを実装するために「IMessageDeserializer」オブジェクトを返すグローバルリポジトリが存在する可能性があります。ロードされたすべてのメッセージクライアントは、サポートするすべてのメッセージのすべてのデシリアライザーをリポジトリに登録し、必要なメッセージタイプIDをメッセージルーターに登録します。

于 2010-01-22T20:13:23.917 に答える
0

すでに述べたように、IDから対応するタイプへのマッピングが必要です。

管理可能な数のメッセージタイプの場合は、バイナリメッセージに格納されているIDの正しいメッセージインスタンスを作成する中央ファクトリを使用できます(たとえば、のようなものを使用しますswitch(messageId))。

私の推測では、巨大なファクトリメソッドの集中化、つまりメッセージの数が多くなるかどうかについて、ほとんど心配していると思います。どういうわけかクラスを登録する必要があることを分散化したい場合は、基本的な考え方について、たとえばこの回答を参照してください。
このアプローチを使用して、サブクラスのファクトリを中央レジストラに登録します。例えば:

// common code:

struct MessageBase {
    virtual ~MessageBase() {}
};

typedef MessageBase* (*MessageConstructor)(char* data);

struct MessageRegistrar {
    static std::map<unsigned, MessageConstructor> messages;
    MessageRegistrar(unsigned id, MessageConstructor f) { 
        messages[id] = f; 
    }
    static MessageBase* construct(unsigned id, char* data) {
        return messages[id](data);
    }
};

#define REGISTER_MESSAGE(id, f) static MessageRegistrar registration_##id(id, f);

// implementing a new message:

struct ConcreteMessage : MessageBase {
    ConcreteMessage(char* data) {}
    static MessageBase* construct(char* data) { 
        return new ConcreteMessage(data); 
    }
};

REGISTER_MESSAGE(MessageId_Concrete, &ConcreteMessage::construct);

// constructing instances from incoming messages:

void onIncomingData(char* buffer) {
    unsigned id = getIdFromBuffer(buffer);
    MessageBase* msg = MessageRestristrar::construct(id, buffer);
    // ...
}
于 2010-01-22T20:15:27.030 に答える