2009-06-19 17 views
3

私は未処理のバイナリメッセージ(非常に単純な、最初のバイトはメッセージタイプ、残りはペイロードです)を取り込むアプリで作業しています。私が達成しようとしているのは、ネットワーキングサービスがアプリケーションの他の部分から抽象化されていることを確認して、プロトコルを今すぐ変更して残りのアプリケーションにあまり影響を与えないようにすることです。 アプリケーションのコンテキストは非常にシンプルなクライアント/サーバーゲームです。私は今クライアントの仕事をしています。ネットワークプロトコルをカプセル化

私はちょっと苦労しています。私はちょうどかなりのオブジェクト(私は思う)を返すトランスレータ/アダプタサービスへの接続を投げるエレガントな方法を見つける必要があります。これらのオブジェクトは、アプリケーションの残りの部分で消費を待っているキューにスローされます。私が直面してる問題がある多かれ少なかれ、この構築物(擬似コード):

はのは、各メッセージが20バイトであると仮定しましょう、私は各20バイトのため、この関数の呼び出しに対処することができます:

public Message GetMessage(byte[] buffer) 
{ 
    switch(buffer[0]) 
    { 
    case 1: 
     return Message1(...); 
    case 2: 
     return Message2(...); 
    ..... 

    case n: 
     return MessageN(...); 
    } 
} 

を明らかに、私はケースの列挙型または定数を使用しますが、それは私を悩ませていることではありません。 ここに事があります。私は約50種類のメッセージがあると思います。これは50件のswitch文を取得することを意味します。私はこれをより小さな部分に切り分ける適切な方法を本当に考えることができず、巨大でエラーが起こりやすい方法になります。私は何も見つけることができなかったので、これを簡単にするパターンがあるかどうか疑問に思っていました。

事前にお問い合わせいただきありがとうございます。

答えて

1

まあ、確かに多くの方法があります。標準的なものは、関数を辞書に格納することです。関数型言語では、

import MyProtocol 

handler = { mListFirmware : listFirmwareVersions,     
      mLoadFirmware : startLoadingFirmwareVersion, 
      mLoadFirmwareBl: loadFirmwareVersionBlock, 
      ... 
} 

... 
try { 
    handler[message[0]](message[1:]) 
} catch (NotInTheDictionary e) { 
    # complain ... 
} 

あなたのC/C++/C#のバージョンが分かりません。そこに関数を置くことができない場合は、関数へのポインタを入れてください。あなたのいくつかの機能が非常に小さい場合は、lambdaで右が、その後に置くことができるいくつかの言語で:

... 
      mLoadFirmware : (lambda (m): start_load(m[1:3]); do_threads()), 
... 

私はどうしたらより多くの最適化があります。すべてのメッセージについて、定数と関数名があります。

Messages = new Array() 

def listFirmwareVersions(m): 
     ... 
    Messages.add(Name_Of_This_Method(), This_Method) 
    # it's possible to get name of current function in Python or C# 

... # how to send 
send_message(Messages.lookup(listFirmwareVersions), ...) 

... # how to receive 
try { 
    Messages[message[0]](message[1:]) 
... 

しかし、あなたは哲学的に正しいことをしたい場合は、ハンドラの別々のクラスを持つことができます:あなたは、しかし、繰り返す必要はありません

class MessageHandler: 
     static int message_handlers = [] 
     int msg 
     Array data 
     void handler 
     Message(a_handler):    
      msg = message_handlers.add(this) 
      handler = a_handler 
     write(Stream s): 
      s.write(msg, data) 

    listFirmwareVersions = new MessageHandler(do_firmware) 
    startLoadingFirmwareVersion = new MessageHandler(load_firmware) 
    ... 

... # how to send 
listFirmwareVersions.send(...) 

... # how to receive 
try { 
     message_handlers[message[0]](message[1:]) 
... 
+0

これは別のハンドラクラスを含む私が行ったオプションです。それぞれのメッセージを扱うロジックのビットを分離する最善の方法と思われます。 –

+0

ネッド・フランダースが言うように、あなたは本ですべてをやっています。 –

1

ファーストバイトの値を使用してインデックスを作成する50個の関数ポインタ(つまりC#デリゲート)の配列、またはキーが最初のバイトの値であるデリゲートの辞書を持つことができます。それは単にswitch文を書くもう一つの方法です。

新しいメッセージタイプ(新しいソースファイルの新しいクラスでもよい)を作成した場合、ソースコードを編集して大きなswitchステートメントに新しいケースを追加するのではなく、既存のメソッドを呼び出して、デリゲートの静的コレクションに新しいデリゲートを追加することができます。

2

私はこれを行ういくつかのJavaコードを持っています。うまくいけば、C#に簡単に翻訳できます。これは、それらに対応するMessageクラスへのメッセージIDからのマップがある

private final Map<Byte, Class<? extends Message>> messageMap; 

:基本的に、私はmessageMapコレクションを持っています。作成するためにリフレクションを使用し、その後、私はmessageMapでインスタンス化する必要がありMessageクラスを調べ、そして、メッセージは、私はワイヤーオフメッセージIDをお読み到着後

public void addMessage(int id, Class<? extends Message> messageClass) { 
    messageMap.put((byte) id, messageClass); 
} 

:私は、それぞれ異なるメッセージタイプに対して一度addMessageを呼び出しますそのクラスのインスタンス。

Class<? extends Message> messageClass = messageMap.get(id); 
Message     message  = messageClass.newInstance(); 

ここでnewInstance()はデフォルトのコンストラクタを呼び出します。

私はこのメッセージ処理コードを、メッセージの異なる複数のアプリケーションで使用しました。それぞれがちょうどそうのような異なるメッセージを登録するコードの素敵な、単純なブロックがあります。

// Messages that we can send to the client. 
addOutgoingMessage(0, HeartbeatMessage.class); 
addOutgoingMessage(1, BeginMessage .class); 
addOutgoingMessage(2, CancelMessage .class); 

// Messages that the client can send. 
addIgnoredMessage (0, HeartbeatMessage.class); 
addIncomingMessage(1, StatusMessage .class, statusMessageHandler); 
addIncomingMessage(2, ProgressMessage .class, progressMessageHandler); 
addIncomingMessage(3, OutputMessage .class, outputMessageHandler); 
addIncomingMessage(4, FinishedMessage .class, finishedMessageHandler); 
addIncomingMessage(5, CancelledMessage.class, cancelledMessageHandler); 
addIncomingMessage(6, ErrorMessage .class, errorMessageHandler); 
1

正確にC#ソリューションは、私は最近、似たような状況に対処していないが。 私の解決策はF#を使用することでした。例えば

は、私のコードは、この

member private this.processDefaultGroupMessage(m : Message) = 
     try 
      match m.Intro.MessageType with 
      | (1us) -> this.listFirmwareVersions(m)        //ListFirmwareVersions    0 
      | (2us) -> this.startLoadingFirmwareVersion(m)      //StartLoadingFirmwareVersion  1 
      | (3us) -> this.loadFirmwareVersionBlock(m)       //LoadFirmwareVersionBlock   2 
      | (4us) -> this.removeFirmwareVersion(m)        //RemoveFirmwareVersion    3 
      | (5us) -> this.activateFirmwareVersion(m)       //ActivateFirmwareVersion   3   
      | (12us) -> this.startLoadingBitmapLibrary(m)       //StartLoadingBitmapLibrary   2 
      | (13us) -> this.loadBitmapBlock(m)         //LoadBitmapLibraryBlock   2   
      | (21us) -> this.listFonts(m)           //ListFonts       0 
      | (22us) -> this.loadFont(m)           //LoadFont       4 
      | (23us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveFont      3 
      | (24us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //SetDefaultFont     3   
      | (31us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ListParameterSets     0 
      | (32us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //LoadParameterSets     4 
      | (33us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //RemoveParameterSet    3 
      | (34us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //ActivateParameterSet    3 
      | (35us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetParameterSet     3   
      | (41us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //StartSelfTest      0 
      | (42us) -> this.ackResponse(m)          //GetStatus (reply with ACK)  0 
      | (43us) -> this.getStatusDetail(m)         //GetStatusDetail     0 
      | (44us) -> this.resetStatus(m)          //ResetStatus      5 
      | (45us) -> this.setDateTime(m)          //SetDateTime      6 
      | (46us) -> this.nakResponse(m, VPL_FRAMELENGTH)      //GetDateTime      0 
      | (71us) -> this.clearConfiguration(m)        //ClearConfiguration    0 
      | (72us) -> this.defineTextFields(m)         //DefineTextFields     11 
      | (74us) -> this.defineClockFields(m)         //DefineClockFields     13 
      | (80us) -> this.deleteFieldDefinitions(m)       //DeleteFieldDefinitions   14 
      | (91us) -> this.preloadTextFields(m)         //PreloadTextFields     15 
      | (94us) -> this.clearFields(m)          //ClearFields      17 
      | (95us) -> this.activate(m)           //Activate       0 
      | _ -> this.nakResponse(m, VPL_REQUESTNOTSUPPORTED) 
     with 
      | _ -> this.nakResponse(m, VPL_INVALID) 

のように見えるこれは完璧な解決策ではないのですが、C#でswitch文よりもずっと良く見えます。 アプリケーション全体がcsharpで書かれていますが、メッセージパーサーはfsharpで書かれています。

はFYI: 我々は、いくつかのインターフェースを有する: - RS232またはTCP/IP

IDataProcessorを介してデータを受信するための責任 -

IDataTransportServerバイナリデータを解析し、メッセージクラスのインスタンスにそれを回すための責任を

IMessageProcessor - メッセージの処理を担当しています(これはfsharpモジュールです)

これはあなたには役に立ちますが、あなたはこの種の問題にどう対処しているのですか?

+0

これは単に書くためそれほど冗長な方法でありますスイッチ、そう?しかし、ニースのオプション! –

+0

ねえ、この場合はありますが、実際ははるかに強力です。そして、私はそれをより良くするために差別的な組合を使うことができると言われました。とにかく私が例を見つけることができるかどうか見てみましょう – TimothyP

+0

私が持っていた例は、pastebinから削除されていますが、基本的にスイッチよりももっと強力な複数の変数を同時に切り替えることができます。あなたにも制限があります。好き| m> 10 && m < 20 ->() – TimothyP