2

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

路由消息的类知道消息类型。每条消息都继承了包含消息 ID 的 Message 类,当然还添加了自己的参数。

问题是,如何将消息从缓冲区传输为正确类型的实际消息实例?

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

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

有什么建议么?

4

4 回答 4

0

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

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

不过我的猜测是,您最担心的是巨型工厂方法的集中化,即消息数量是否变大。如果您想分散您需要以某种方式注册您的课程,请参阅例如this answer了解基本思想。
使用这种方法向中央注册器注册您的子类的工厂。例如:

// 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);
    // ...
}
于 2010-01-22T20:15:27.030 回答
0

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

于 2010-01-22T20:10:30.757 回答
0

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

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

于 2010-01-22T20:11:04.420 回答
0

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

IMessageInterface NarrowToInterface(MessageId id);

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

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

MessageId GetMessageType();

路由器将使用它来了解它是什么消息类型来决定将它路由到哪里。更多关于稍后如何实施的信息。

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

如果消息的类型没有明确定义或将来可能会增长,那么您将不得不在某个时候忍受(编译器无法验证)向下转换。据我所知,我建议的实现尽可能地封装了这一点,并将耦合限制在绝对最小值。

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

于 2010-01-22T20:13:23.917 回答