Двойная диспетчеризация C ++, Factory Pattern или способ автоматизации создания производных объектов из полученных последовательных данных

Я работаю над коммуникационной библиотекой C ++, в которой я получаю сериализованные данные от ряда устройств (сетевых сокетов, сетей uart / usb, CAN и LIN). Мне также нужно создать сериализованные данные из моих объектов сообщений.

У меня есть базовый класс с именем MessageBase, из которого в настоящее время у меня есть два производных класса с именами Message и CtrlMessage. Проекту в конечном итоге понадобится еще несколько типов сообщений в будущем, и поэтому я ищу реализацию с использованием шаблона проектирования, который позволяет легко расширяться до новых типов сообщений в будущем.

Моя другая цель, как говорит Скотт Мейес, трудно использовать классы неправильно и легко использовать правильно.

Я начал изучать использование шаблона NVI и использование фабрики C ++ для создания сообщений, однако классу Factory понадобилось бы обрабатывать некоторую десериализацию заголовка, чтобы выяснить, какой тип сообщения находится в полезной нагрузке.

class MessageBase
{
private:
// other public & private methods omitted for brevity
MessageBase &ISerialize( dsStream<byte> &sdata) = 0;
public:
MessageBase &Serialize( dsStream<byte> &sdata)
{
ISerialize(sdata);
}

}

class Message : public MessageBase
{
private:
// other public & private methods omitted for brevity
MessageBase &ISerialize( dsStream<byte> &sdata);
public:
}

class MessageFactory
{
private:

public:

CreateMessageFromStream( dsStream<byte> &RxData)
{
// read N bytes from RxData to determine type and then
// jump into switch to build message

switch(MsgType)
{
case MSG_DATA:
{
Message *pMsg = new Message(RxData);
}
break;

case MSG_CTRL:
{
MessageCtrl *pMsg = new MessageCtrl(RxData);
}
break;
}
}

// I shorten quite a bit of this to, hopefully, give the basic idea.

Другой подход, который я изучал, — «Двойная рассылка», как обрисовано в общих чертах Скоттом Мейерсом № 33 в его книге «Более эффективный C ++». Но это, по-видимому, только смещает проблему: либо требуется, чтобы все производные классы одного уровня знали друг о друге, либо более продвинутое решение с stl map для эмуляции vtable. Этот код выглядит ужасно и трудно следовать.

Я взглянул на шаблон посетителя C ++ и шаблон Builder Creational, и все они требуют, чтобы вызывающая сторона заранее знала, какой тип производного типа Message вы хотите создать.

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

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

Какие-либо предложения?

2

Решение

Я не знаю, стоит ли это того. Но машинное оборудование, чтобы делать то, что вы хотите, может быть написано.

Начнем с MessageBase, Оно имеет private конструктор.

Вы тогда говорите это сделать MessageHelper<T> быть friend учебный класс.

MessageHelper выглядит так:

enum MessageType {
TYPE1, // notice no assigment
TYPE2, // values should be consecutive, distinct, and start at `0`
TYPE3, // or things go poorly later on.
NUM_TYPES /* should be last */
};
template<MessageType> struct MessageTag {}; // empty, for overloading
template<MessageType...> struct MessageTags {};
template<MessageType Last, MessageType... List> struct MakeMessageTags:
MakeMessageTags<MessageType(Last-1), MessageType(Last-1), List...>
{};
template<MessageType... List> struct MakeMessageTags<MessageType(0), List...>:
MessageTags<List...>
{};
typedef MessageBase*(*MessageCreatorFunc)(dsStream<byte>&);

// write this somewhere, next to a given type.  If you don't, code later will fail to compile
// (yay).  You could make a macro to write these:
MessageCreatorFunc MessageCreator( MessageTag<TYPE1> ) {
return []( dsStream<byte>& st )->MessageBase* {
return new MessageType1(st);
};
}

// manual compile time switch:
template<MessageType... List>
MessageBase* CreateMessageFromStream_helper( MessageType idx, dsStream<byte>& st, MessageTags<List...> )
{
static MessageCreatorFunc creator[] = { MessageCreator(MessageTag<List>())... };
return creator[idx]( st );
}
MessageBase* CreateMessageFromStream( dsStream<byte>& st ) {
// stuff, extract MessageType type
MessageBase* msg = CreateMessageFromStream_helper( type, st, MakeMessageTags<MessageType::NUM_TYPES>() );
// continue
}

эффект приведенного выше кода заключается в том, что мы автоматически создаем таблицу переходов вручную для создания наших сообщений.

Если никто не пишет MessageCreator( MessageTag<TYPE> ) перегрузка или она не видна в контексте _helperвышеуказанное не компилируется. Таким образом, это гарантирует, что если вы добавите новый тип сообщения, вы напишите код создания или нарушите сборку. Гораздо лучше, чем где-то скрытый оператор switch.

В какой-то момент должна быть связь между MessageType и тип C ++, который должен быть создан: вышеупомянутый механизм просто гарантирует, что если эта связь не установлена, мы получим ошибку компилятора.

Вы можете немного повеселиться и получить лучшее сообщение, вместо того, чтобы перегружать MessageCreator, специализируясь на:

template<MessageType TYPE>
void MessageCreator( MessageTag<TYPE> ) {
static_assert( "You have failed to create a MessageCreator for a type" );
}
// specialization:
template<>
MessageCreatorFunc MessageCreator( MessageTag<TYPE1> ) {
return []( dsStream<byte>& st )->MessageBase* {
return new MessageType1(st);
};
}

который немного более тупой, но может генерировать лучшее сообщение об ошибке. (в то время как template<> не требуется для всех случаев, так как переопределение также заменяет templateпо крайней мере, по стандарту один такая специализация, которая может компилироваться, должна существовать, или программа плохо сформирована, диагноз не требуется (!?)).

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]