Десериализация неизвестного унаследованного типа [C ++] - PullRequest
2 голосов
/ 22 января 2010

Допустим, у меня есть класс, который направляет сообщения их обработчикам. Этот класс получает сообщения от другого класса, который получает сообщения через сокет. Итак, сокет получает буфер, содержащий какое-то сообщение.

Класс, который маршрутизирует сообщения, знает о типах сообщений. Каждое сообщение наследует класс Message, который содержит идентификатор сообщения и, конечно, добавляет свои собственные параметры.

Проблема в том, как я могу передать сообщение из буфера в актуальный экземпляр сообщения правильного типа?

Например, у меня есть DoSomethingMessage, который наследует сообщение. Я получил буфер, содержащий сообщение, но мне нужно каким-то образом преобразовать буфер обратно в DoSomethingMessage, даже не зная, что это DoSomethingMessage.

Я мог бы перенести буфер в MessageRouter и проверить его по ID и создать правильный экземпляр, но для меня это выглядит как очень плохой дизайн.

Есть предложения?

Ответы [ 4 ]

0 голосов
/ 22 января 2010

Как уже упоминалось, вам нужно каким-то образом сопоставить идентификатор с соответствующим типом.

Для управляемого количества типов сообщений вы можете просто использовать центральную фабрику, которая создает правильный экземпляр сообщения для идентификатора, сохраненного в двоичном сообщении (например, используя что-то вроде switch(messageId)).

Полагаю, вы в основном беспокоитесь о централизации гигантского фабричного метода, т. Е. Если количество сообщений становится большим. Если вы хотите децентрализовать то, что вам нужно как-то зарегистрировать свои классы, см., Например, этот ответ для основной идеи.
Используйте этот подход, чтобы зарегистрировать фабрику для своего подкласса у центрального регистратора. 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);
    // ...
}
0 голосов
/ 22 января 2010

Как вы можете преобразовать любые данные в логическое представление, если вы не знаете, что эти данные намереваются представить? Если я отправлю вам 0x2FD483EB, у вас не будет возможности узнать, что это значит, если вы не знаете, что я намерен с ним представлять - возможно, это одно 32-разрядное число, может быть пара 16-разрядных чисел, возможно, строка 4 8-битных символа.

Поскольку вы получаете необработанные данные из сокета, вы не можете полагаться на магию компилятора, используемую для полиморфизма. Все, что вы можете сделать, это прочитать идентификатор и создать соответствующий класс, используя старый добрый switch. Вы можете, конечно, обернуть это в хороший объектно-ориентированный слой, сделав дочерние классы ответственными за распознавание их собственного идентификатора и фабричного класса для создания соответствующего класса.

0 голосов
/ 22 января 2010

Вы можете абстрагировать десериализацию сообщения. Иметь класс «MessageHolder», который изначально имеет буфер для объекта. Это будет иметь метод:

IMessageInterface NarrowToInterface (идентификатор сообщения);

Мне не было ясно, узнает ли ваш маршрутизатор, что это за сообщение или нет. Если это произойдет, то он получит экземпляр держателя сообщения и вызовет для него метод NarrowToInterface.

Это передало бы идентификатор соответствующего типа. Если маршрутизатор не знает, какой это был тип, у вас также будет свойство для объекта MessageHolder:

MessageId GetMessageType ();

что маршрутизатор будет использовать для определения типа сообщения, чтобы решить, куда его направить. Подробнее о том, как это реализовано позже.

IMessageInterface - это абстрактный класс или интерфейс, который получатель сообщения будет преобразовывать в соответствующий тип, поскольку он будет знать, какой тип ожидать. Если все различные сообщения хорошо известны и у вас есть универсальные шаблоны или шаблоны, вы можете использовать метод NarrowToInterface как метод шаблона, который принимает возвращаемое значение в качестве параметра шаблона, чтобы обеспечить лучшую безопасность типов. Если у вас нет шаблонов, вы можете использовать технику двойной отправки шаблона «Vistor». Google "двойная рассылка посетитель" для получения дополнительной информации.

Если типы сообщений не определены четко или могут вырасти в будущем, вам просто придется смириться с (не проверяемым компилятором) понижением в какой-то момент. Насколько я знаю, реализация, которую я предлагаю, инкапсулирует это в максимально возможной степени и ограничивает связь до абсолютного минимума.

Кроме того, чтобы это работало, ваши сообщения должны быть снабжены стандартным идентификатором в заголовке. то есть существует стандартный заголовок, который имеет длину всего сообщения, а также идентификатор типа сообщения. Таким образом, конечная точка сокета может проанализировать основы сообщения и поместить его в держатель сообщения. MessageHolder может либо сам знать обо всех различных типах сообщений для реализации метода NarrowToInterface (), либо может существовать глобальное хранилище, которое будет возвращать объекты IMessageDeserializer для реализации NarrowToInterface для каждого типа сообщения. Все загруженные клиенты сообщений регистрируют все десериализаторы для всех сообщений, которые они поддерживают в репозитории, а также регистрируют идентификаторы типов сообщений, которые они хотят, в маршрутизаторе сообщений.

0 голосов
/ 22 января 2010

Если вы передаете сообщение через сокет, вам нужно передать какой-то тег, который будет определять тип передаваемого вами сообщения. Таким образом, когда вы читаете данные из сокета, вы знаете, какой тип объекта вам нужно создать. Ваш код должен знать, какое сообщение нужно создать. Двоичные двоичные объекты, которые поступают из сокета, не содержат информации о том, что это такое.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...