【问题标题】:Choosing the right subclass to instantiate programmatically选择正确的子类以编程方式实例化
【发布时间】:2009-11-13 23:55:19
【问题描述】:

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

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

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

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

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

【问题讨论】:

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


    【解决方案1】:

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

    代码如下所示:

    // 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;
        }
    };
    

    【讨论】:

    • 确实是非常有趣的方法。运行时更改方面在我的特定情况下并不是真正有用,但它是一个很好的奖励,在其他一些情况下可能会证明有用。在确认已回答之前,我会稍等片刻,看看是否有其他人。
    • 显然你不能在运行时添加消息类型。反正switch case已经转化为每个message type注册入口一行进map了,看不到这里的大胜,肯定不会少结果代码量,效率更高?可能不太混乱?
    • 我不是很关心性能,我不是每秒处理数十万条消息,这里的代码风格更受关注。另外,我在这里看到的胜利(即使从问题中看不明显)是,如果我需要其他类似的东西,我还可以添加除“createMessage”之外的其他方法,例如定义哪个“控制类”将处理消息和东西。对于运行时更改:您可以在运行时修改地图。要添加新消息,您可以从插件等共享对象中动态加载它们。
    • 剩下的一个问题是我仍然需要在 Message{A,B} 构造函数中重复“类型”(在构建消息时不是反序列化)。除非我也为此浏览地图,但这在代码中可能不那么漂亮。
    【解决方案2】:

    事实上,这是一个非常基本的问题(正如您可以想象的那样,您绝对不是唯一一个在 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>();
    

    现在是不是很漂亮:)?

    【讨论】:

      猜你喜欢
      • 2011-03-12
      • 2013-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-26
      • 2020-12-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多