6

OK、コンテキストは、バイトストリームを解析して、操作しやすい「オブジェクト」表現に変換するシリアライゼーション/デシリアライゼーションコードです (逆も同様です)。

以下は、基本メッセージ クラスを使用した単純化された例です。次に、「タイプ」ヘッダーに応じて、さらにいくつかのデータ/関数が存在するため、インスタンス化する適切なサブクラスを選択する必要があります。

class BaseMessage {
public:
    enum Type {
        MyMessageA = 0x5a,
        MyMessageB = 0xa5,
    };

    BaseMessage(Type type) : mType(type) { }
    virtual ~BaseMessage() { }

    Type type() const { return mType; } 

protected:
    Type mType;

    virtual void parse(void *data, size_t len);
};

class MyMessageA {
public:
    MyMessageA() : BaseMessage(MyMessageA) { }

    /* message A specific stuf ... */

protected:
    virtual void parse(void *data, size_t len);
};

class MyMessageB {
public:
    MyMessageB() : BaseMessage(MyMessageB) { }

    /* message B specific stuf ... */

protected:
    virtual void parse(void *data, size_t len);
};

実際の例では、いくつかのメッセージがフィールド/機能を互いに共有するため、何百もの異なるメッセージ タイプと、場合によってはいくつかのレベルまたは階層が存在します。

さて、バイト文字列を解析するために、次のようなことをしています:

BaseMessage *msg = NULL;
Type type = (Type)data[0];

switch (type) {
    case MyMessageA:
        msg = new MyMessageA();
        break;

    case MyMessageB:
        msg = new MyMessageB();
        break;

    default:
        /* protocol error */
}

if (msg)
    msg->parse(data, len);

しかし、私はこの巨大なスイッチが非常に洗練されているとは思いません。また、どのメッセージがどの「型の値」を持つかについての情報を 2 回 (コンストラクターで 1 回、このスイッチで 1 回) 持っています。

より良い方法を探しています...これを改善するにはどうすればよいですか?

4

2 に答える 2

6

実際、これはかなり基本的な質問です(ご想像のとおり、C ++で逆シリアル化するのはあなただけではありません)。

あなたが探しているものは仮想建設と呼ばれています。

C ++は仮想構造を定義していませんが、PrototypeデザインパターンまたはFactoryメソッドを使用して簡単に近似できます。

Factory私は個人的にこのアプローチを好みます。なぜなら、 Prototype1つは複製され、次に定義されたある種のデフォルトインスタンスを持つことを意味するからです...問題は、すべてのクラスが意味のあるデフォルトを持っているわけではなく、さらに言えば、意味のあるものを持っているわけではないということですDefault Constructor

Factoryアプローチは十分に簡単です。

  • メッセージ用とパーサー用の共通の基本クラスが必要です
  • 各メッセージには、タグと関連するパーサーの両方があります

いくつかのコードを見てみましょう:

// Framework
class Message
{
public:
  virtual ~Message();
};

class Parser
{
public:
  virtual ~Parser();
  virtual std::auto_ptr<Message> parse(std::istream& serialized) const;
};

// Factory of Messages
class MessageFactory
{
public:
  void register(std::string const& tag, Parser const& parser);
  std::auto_ptr<Message> build(std::string const& tag, std::istream& serialized) const;
private:
  std::map<std::string,Parser const*> m_parsers;
};

そして、このフレームワーク(確かに単純)では、いくつかの派生クラス:

class MessageA: public Message
{
public:
  MessageA(int a, int b);
};

class ParserA: public Parser
{
public:
  typedef std::auto_ptr<MessageA> result_type;
  virtual result_type parse(std::istream& serialized) const
  {
    int a = 0, b = 0;
    char space = 0;
    std::istream >> a >> space >> b;
    // Need some error control there
    return result_type(new MessageA(a,b));
  }
};

そして最後に、使用:

int main(int argc, char* argv[])
{
  // Register the parsers
  MessageFactory factory;
  factory.register("A", ParserA());

  // take a file
  // which contains 'A 1 2\n'
  std::ifstream file = std::ifstream("file.txt");
  std::string tag;
  file >> tag;
  std::auto_ptr<Message> message = factory.parse(tag, file);

  // message now points to an instance of MessageA built by MessageA(1,2)
}

それは機能します、私はそれ(またはバリエーション)を使用することを知っています。

考慮すべきことがいくつかあります。

  • シングルトンを作成することをいとわない場合がありますMessageFactory。これにより、ライブラリのロード時に呼び出すことができるため、静的変数をインスタンス化することでパーサーを登録できます。mainこれは、すべてのパーサータイプを登録する必要がない場合に非常に便利です。ローカリティ>依存関係が少なくなります。
  • タグは共有する必要があります。タグがMessageクラスの仮想メソッド(タグと呼ばれる)によって提供されることも珍しくありません。

好き:

class Message
{
public:
  virtual ~Message();
  virtual const std::string& tag() const = 0;
  virtual void serialize(std::ostream& out) const;
};
  • シリアル化のロジックも共有する必要があります。オブジェクトが独自のシリアル化/逆シリアル化を処理することは珍しくありません。

好き:

class MessageA: public Message
{
public:
  static const std::string& Tag();
  virtual const std::string& tag() const;
  virtual void serialize(std::ostream& out) const;

  MessageA(std::istream& in);
};

template <class M>
class ParserTemplate: public Parser // not really a parser now...
{
public:
  virtual std::auto_ptr<M> parse(std::istream& in) const
  {
    return std::auto_ptr<M>(new M(in));
  }
};

テンプレートの優れている点は、私を驚かせるのが止まらないことです。

class MessageFactory
{
public:
  template <class M>
  void register()
  {
    m_parsers[M::Tag()] = new ParserTemplate<M>();
  }
};

//skipping to registration
  factory.register<MessageA>();

今それはきれいではありませんか:)?

于 2009-11-14T20:40:39.330 に答える