选择正确的子类以编程方式实例化

时间:2009-11-13 23:55:19

标签: c++ design-patterns polymorphism serialization subclassing

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

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

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 个答案:

答案 0 :(得分: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;
    }
};

答案 1 :(得分:5)

这实际上是一个非常基本的问题(你可以想象,你绝对不是唯一一个在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&gt;少依赖。
  • 必须共享标签。标记由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>();

现在不是很漂亮:)?