コード内でさまざまな構造体/クラスとして表現している一連の可変長記述子を含むバイトストリームから読み取っています。各記述子には、そのタイプを識別するために使用される他のすべての記述子と共通の固定長ヘッダーがあります。
各記述子を最適に解析および表現し、そのタイプに応じて適切なアクションを実行するために使用できる適切なモデルまたはパターンはありますか?
コード内でさまざまな構造体/クラスとして表現している一連の可変長記述子を含むバイトストリームから読み取っています。各記述子には、そのタイプを識別するために使用される他のすべての記述子と共通の固定長ヘッダーがあります。
各記述子を最適に解析および表現し、そのタイプに応じて適切なアクションを実行するために使用できる適切なモデルまたはパターンはありますか?
私はこれらのタイプのパーサーをたくさん書いてきました。
固定長ヘッダーを読んでから、単純なswitch-caseを使用して構造体の正しいコンストラクターにディスパッチし、固定ヘッダーとストリームをそのコンストラクターに渡して、ストリームの可変部分を消費できるようにすることをお勧めします。
これは、ファイル解析でよくある問題です。通常、記述子の既知の部分(この場合は固定長ですが、常にそうであるとは限りません)を読み取り、そこで分岐します。通常、ここでは戦略パターンを使用します。これは、システムが広く柔軟であると一般的に期待しているためですが、ストレートスイッチまたはファクトリも機能する可能性があります。
もう1つの質問は、ダウンストリームコードを制御し、信頼していますか?意味:ファクトリ/戦略の実装?そうした場合、ストリームと消費する予定のバイト数を指定するだけで済みます(おそらく、デバッグアサーションを配置して、正確に正しい量を読み取ることを確認します)。
ファクトリ/ストラテジーの実装を信頼できない場合(おそらく、ユーザーコードにカスタムデシリアライザーの使用を許可する場合)、ストリームの上にラッパーを構築します(例:SubStream
protobuf-netから)。これは、期待されるものだけを許可します。消費されるバイト数(後でEOFを報告)。このブロック外でのseek/etc操作を許可しません。また、(リリースビルドでも)十分なデータが消費されていることを実行時チェックしますが、この場合、おそらく未読データを超えて読み取るだけです。つまり、ダウンストリームコードが20バイトを消費すると予想した場合、12バイトしか読み取れません。 、次に次の8をスキップして、次の記述子を読み取ります。
それを拡張するには; ここでの1つの戦略設計には、次のようなものがあります。
interface ISerializer {
object Deserialize(Stream source, int bytes);
void Serialize(Stream destination, object value);
}
予想されるマーカーごとにそのようなシリアライザーのディクショナリ(または数が少ない場合はリスト)を作成し、シリアライザーを解決してから、Deserialize
メソッドを呼び出すことができます。マーカーがわからない場合は、次のいずれかを使用してください。
上記の補足として、このアプローチ(戦略)は、システムが実行時にリフレクションまたはランタイムDSL(など)を介して決定される場合に役立ちます。システムがコンパイル時に完全に予測可能である場合(変更されないため、またはコード生成を使用しているため)、ストレートswitch
アプローチの方が適切な場合があります。おそらく、追加のインターフェイスは必要ありません。適切なコードを直接挿入できます。
覚えておくべき重要なことの1つは、ストリームから読み取っていて、有効なヘッダー/メッセージが検出されない場合は、最初のバイトだけを破棄してから再試行することです。代わりに、パケット全体またはメッセージ全体が破棄されるのを何度も目にしました。これにより、有効なデータが失われる可能性があります。
これは、ファクトリメソッドまたはおそらくアブストラクトファクトリの仕事のように思えます。ヘッダーに基づいて、呼び出すファクトリメソッドを選択します。これにより、関連するタイプのオブジェクトが返されます。
これが単にコンストラクターをswitchステートメントに追加するよりも優れているかどうかは、作成するオブジェクトの複雑さと均一性によって異なります。
私は提案します:
fifo = Fifo.new while(fdは読み取り可能){ fdからすべてを読み取り、FIFOに貼り付けます if(FIFOの前面に有効なヘッダーがあり、 fifoはペイロードに対して十分な大きさです){ コンストラクターをディスパッチし、FIFOからバイトを削除します } }
この方法では:
優れたOOにしたい場合は、オブジェクト階層でビジターパターンを使用できます。私がそれをどのように行ったかは次のようでした(ネットワークからキャプチャされたパケットを識別するために、あなたが必要とするかもしれないものとほとんど同じこと):
1つの親クラスを持つ巨大なオブジェクト階層
各クラスには、その親に登録する静的コンストラクターがあるため、親はその直接の子について知っています(これは、c ++でした。この手順は、リフレクションが適切にサポートされている言語では必要ないと思います)
各クラスには、バイトストリームの残りの部分を取得する静的コンストラクターメソッドがあり、それに基づいて、そのデータを処理するのは彼の責任であるかどうかを決定しました。
パケットが届いたとき、私はそれをメインの親クラス(Packetと呼ばれる)の静的コンストラクターメソッドに渡すだけでした。このメソッドは、そのパケットを処理する責任があるかどうかをすべての子にチェックしました。これは、1つになるまで再帰的に行われました。階層の最下部にあるクラスは、インスタンス化されたクラスを返しました。
静的な「コンストラクター」メソッドはそれぞれ、バイトストリームから独自のヘッダーを切り取り、ペイロードのみをその子に渡します。
このアプローチの利点は、他のクラスを表示/変更しなくても、オブジェクト階層のどこにでも新しいタイプを追加できることです。パケットに対しては非常にうまく機能しました。それはこのようになりました:
あなたがその考えを見ることができることを願っています。