反序列化未知的继承类型[C ++]

时间:2010-01-22 19:54:17

标签: c++ message

假设我有一个将消息路由到其处理程序的类。这个类从另一个通过套接字获取消息的类获取消息。因此,套接字获取包含某种消息的缓冲区。

路由消息的类知道消息类型。每条消息都继承Message类,其中包含消息ID,当然也会添加它自己的paraemters。

问题是,如何将缓冲区中的消息转移为正确类型的actucal消息实例?

例如,我有一个继承Message的DoSomethingMessage。我得到包含消息的缓冲区,但我需要以某种方式将缓冲区转换回DoSomethingMessage,而不是真的知道它是DoSomethingMessage。

我可以将缓冲区传输到MessageRouter,并通过ID进行检查并创建正确的实例,但这对我来说似乎是一个非常糟糕的设计。

有什么建议吗?

4 个答案:

答案 0 :(得分:0)

如果您通过套接字传递消息,则需要传递一些标记,以标识您传递的消息类型。这样,当您从套接字读取数据时,您就知道需要创建什么类型的对象。你的代码必须知道它需要创建什么样的消息。来自套接字的二进制blob不包含有关它的信息。

答案 1 :(得分:0)

如果您不知道数据首先要表示什么,那么如何将任何数据转换为逻辑表示?如果我发给你0x2FD483EB,除非你知道我打算用它代表什么,否则你无法知道它意味着什么 - 也许它是一个32位数字,也许是一对16位数字,也许是4个8位字符的字符串。

由于您从套接字获取原始数据,因此您不能依赖用于多态的编译器魔法。您所能做的就是阅读ID并使用旧的switch创建适当的类。当然,您可以将它包装在一个漂亮的面向对象的层中,使子类负责识别自己的ID和工厂类来创建适当的类。

答案 2 :(得分:0)

您可以抽象消息反序列化。有一个“MessageHolder”类,它最初只有对象的缓冲区。它会有一个方法:

  

IMessageInterface NarrowToInterface(MessageId id);

我不清楚你的路由器是否已经知道它是什么类型的消息。如果是,那么它将接收消息持有者实例并在其上调用NarrowToInterface方法。

它将传递适当类型的id。如果路由器不知道它是什么类型,那么你在MessageHolder对象上也有一个属性:

  

MessageId GetMessageType();

路由器将使用它来了解决定将其路由到何处的消息类型。更多关于如何在以后实施的内容。

IMessageInterface是一个抽象类或接口,消息的接收者将向下转换为适当的类型,因为它会知道期望的类型。如果所有不同的消息都是众所周知的,并且您可以使用泛型或模板,则可以将NarrowToInterface方法作为模板方法,将返回值作为模板参数,这样您就可以获得更好的类型安全性。如果没有模板,可以使用“Vistor”模式的双重调度技术。谷歌“双重调度访客”获取更多信息。

如果消息的类型没有明确定义或者将来可能会增长,那么在某些时候你只需要使用(编译器无法验证的)转发。据我所知,我建议的实现尽可能地封装了它,并将耦合限制在绝对最小值。

此外,要使其正常工作,您的邮件必须在标题中标有标准标识符。即,有一个标准头,它具有整个消息的长度以及消息类型的ID。这样,套接字端点就可以解析消息的基础知识并将其放入消息持有者中。 MessageHolder可以知道所有不同的消息类型本身来实现NarrowToInterface()方法,或者可能有一个全局存储库,它将返回一个“IMessageDeserializer”对象来为每个消息Type实现NarrowToInterface。所有加载的消息客户端都将为它们支持的所有消息注册所有反序列化器,并使用消息路由器注册它们所需的消息类型ID。

答案 3 :(得分:0)

如前所述,您必须以某种方式具有从id到相应类型的映射。

对于可管理数量的消息类型,您可以使用中央工厂为二进制消息中存储的id创建正确的消息实例(例如使用switch(messageId)之类的内容)。

我的猜测是,你最担心的是巨型工厂方法的集中化,即消息数量是否变大。如果你想分散你需要以某种方式注册你的课程,请参阅例如this answer基本理念。
使用此方法向中央注册商注册子类的工厂。 E.g:

// common code:

struct MessageBase {
    virtual ~MessageBase() {}
};

typedef MessageBase* (*MessageConstructor)(char* data);

struct MessageRegistrar {
    static std::map<unsigned, MessageConstructor> messages;
    MessageRegistrar(unsigned id, MessageConstructor f) { 
        messages[id] = f; 
    }
    static MessageBase* construct(unsigned id, char* data) {
        return messages[id](data);
    }
};

#define REGISTER_MESSAGE(id, f) static MessageRegistrar registration_##id(id, f);

// implementing a new message:

struct ConcreteMessage : MessageBase {
    ConcreteMessage(char* data) {}
    static MessageBase* construct(char* data) { 
        return new ConcreteMessage(data); 
    }
};

REGISTER_MESSAGE(MessageId_Concrete, &ConcreteMessage::construct);

// constructing instances from incoming messages:

void onIncomingData(char* buffer) {
    unsigned id = getIdFromBuffer(buffer);
    MessageBase* msg = MessageRestristrar::construct(id, buffer);
    // ...
}