簡體   English   中英

反序列化未知繼承類型[C ++]

[英]Deserializing unknown inherited type [C++]

可以說我有一個將消息路由到其處理程序的類。 此類從另一個通過套接字獲取消息的類獲取消息。 因此,套接字獲得了包含某種消息的緩沖區。

路由消息的類知道消息類型。 每條消息都繼承了Message類,該類包含一個消息ID,並且當然會添加它自己的參數。

問題是,如何將消息從緩沖區傳輸為正確類型的實際消息實例?

例如,我有一個DoSomethingMessage繼承了Message。 我得到了包含消息的緩沖區,但是我需要以某種方式將緩沖區轉換回DoSomethingMessage,而不真正知道它是DoSomethingMessage。

我本可以將緩沖區轉移到MessageRouter,然后通過ID進行檢查並創建正確的實例,但是對我來說這似乎是一個非常糟糕的設計。

有什么建議么?

如果要通過套接字傳遞消息,則需要傳遞一些標記,該標記將標識所傳遞消息的類型。 這樣,當您從套接字讀取數據時,便知道需要創建哪種類型的對象。 您的代碼必須知道它需要創建什么樣的消息。 來自套接字的二進制Blob不包含有關其含義的信息。

當您首先不知道數據要表示什么時,如何將任何數據轉換為邏輯表示? 如果我給您發送0x2FD483EB ,除非您知道我打算用它表示什么,否則您將無法知道它的含義-可能是單個32位數字,可能是一對16位數字,也許是4 8的字符串位字符。

由於要從套接字獲取原始數據,因此您不能依賴於用於多態的編譯器魔術。 您所能做的就是讀取ID,並使用良好的舊switch創建適當的類。 當然,您可以將其包裝在一個漂亮的面向對象層中,使子類負責識別自己的ID,並使用工廠類來創建適當的類。

您可以抽象化消息反序列化。 有一個“ MessageHolder”類,該類最初僅具有對象的緩沖區。 它會有一個方法:

IMessageInterface NarrowToInterface(MessageId id);

我不清楚您的路由器是否已經知道它是哪種類型的消息。 如果是這樣,則它將接收消息持有者實例並在其上調用NarrowToInterface方法。

它將傳遞適當類型的ID。 如果路由器不知道它是什么類型,那么您還將在MessageHolder對象上擁有一個屬性:

MessageId GetMessageType();

路由器將用來了解其決定路由的消息類型。 有關稍后如何實現的更多信息。

IMessageInterface是一個抽象類或接口,消息的接收者會將其向下轉換為適當的類型,因為它知道期望的類型。 如果所有不同的消息都是眾所周知的,並且您可以使用泛型或模板,則可以將NarrowToInterface方法作為將返回值作為模板參數的模板方法,以使類型安全性更高。 如果沒有模板,則可以使用“ Vistor”模式的雙調度技術。 Google的“雙重派遣訪問者”以獲取更多信息。

如果消息的類型定義不明確或將來可能會增長,則您只需要在某個時候忍受(無法編譯器驗證)的垂頭喪氣。 據我所知,我建議的實現盡可能地對此進行封裝,並將耦合限制在其絕對最小值。

同樣,要使此功能起作用,您的消息必須在標頭中帶有標准標識符。 也就是說,有一個標准標頭,它具有整個消息的長度以及消息類型的ID。 這樣,套接字端點可以解析消息的基礎並將其放入消息持有者。 MessageHolder可以自己知道所有不同的消息類型以實現NarrowToInterface()方法,或者可以有一個全局存儲庫,該存儲庫將返回“ IMessageDeserializer”對象以為每種消息類型實現NarrowToInterface。 所有已加載的消息客戶端將在存儲庫中為其支持的所有消息注冊所有反序列化器,並在消息路由器中注冊所需的消息類型ID。

如前所述,您必須以某種方式從id映射到相應的類型。

對於大量消息類型,您可以僅使用中央工廠來為存儲在二進制消息中的id創建正確的消息實例(例如,使用諸如switch(messageId)類的東西)。

我的猜測是,您最擔心的是巨型工廠方法的集中化,即消息數量是否很大。 如果您想分散管理,需要以某種方式注冊您的班級,請參閱此答案以了解基本思想。
使用這種方法向中央注冊商為您的子類注冊工廠。 例如:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM