6

好的,上下文是一些序列化/反序列化代码,它将字节流解析为更易于使用的“对象”表示(反之亦然)。

这是一个带有基本消息类的简化示例,然后根据“类型”标头,存在更多数据/函数,我们必须选择正确的子类来实例化:

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);

但是我觉得这个巨大的开关不是很优雅,而且我有两次关于哪个消息具有哪个“类型值”的信息(一次在构造函数中,一次在此开关中)它也很长......

我正在寻找一种更好的方法,它会更好......如何改进这个?

4

2 回答 2

10

接近它的一种方法是使用映射并为每种消息类型注册某种工厂函数。这意味着您摆脱了开关盒,可以动态添加和删除消息。

代码看起来像:

// Create the map (most likely a member in a different class)
std::map<BaseMessage::Type, MessageCreator*> messageMap;
...

// Register some message types
// Note that you can add and remove messages at runtime here
messageMap[BaseMessage::MyMessageA] = new MessageCreatorT<BaseMessageA>();
messageMap[BaseMessage::MyMessageB] = new MessageCreatorT<BaseMessageB>();
...

// Handle a message
std::map<Type, MessageCreator*>::const_iterator it = messageMap.find(msgType);
if(it == messageMap.end()) {
    // Unknown message type
    beepHang();
}
// Now create the message
BaseMessage* msg = it->second.createMessage(data);

MessageCreator 类看起来像这样:

class MessageCreator {
    public:
    virtual BaseMessage* createMessage(void* data, size_t len) const = 0;
};
template<class T> class MessageCreatorT : public MessageCreator {
    public:
    BaseMessage* createMessage(void* data, size_t len) const {
        T* newMessage = new T();
        newMessage.parse(data, len);
        return newMessage;
    }
};
于 2009-11-14T00:14:17.250 回答
6

事实上,这是一个非常基本的问题(正如您可以想象的那样,您绝对不是唯一一个在 C++ 中反序列化的人)。

您正在寻找的是所谓的虚拟建筑。

C++ 没有定义虚拟构造,但很容易使用Prototype设计模式或使用Factory方法来近似它。

我个人更喜欢这种Factory方法,因为这种方法Prototype意味着拥有某种被复制并随后定义的默认实例......问题是并非所有类都有一个有意义的默认值,就此而言,一个有意义的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注册每个解析器类型,这将非常方便:locality > less dependencies。
  • 标签必须共享。标记由 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 回答