Моя проблема заключается в следующем: я пишу расширяемый протокол связи между сервером и клиентом. Объекты, которые необходимо передать, используют шаблон проектирования .
. Вот формат класса:
class command {
public:
using id = uint8_t;
using buffer = std::vector<char>;
enum command_t: id {a_command, another_command};
command() = delete;
command(id id): id_(id) {}
static command* unserialize(buffer);
virtual buffer const serialize() const = 0;
virtual void execute() = 0;
protected:
/* Not possible, see further where I explain. Keeping it here for
my example */
virtual static command* do_unserialize(buffer::iterator, buffer::iterator) = 0;
id id_;
};
class a_command;
class another_command;
Каждая команда будет подклассом command
, command_t
id будет связан с каждой командой. В приведенном выше примере a_command
и another_command
также имеют соответствующий класс.
command::serialize
реализован в производных классах. Что он делает, так это записывает всю необходимую информацию о классе в байтовом массиве и возвращает его. Конечно, то же самое относится и к защищенному command::do_unserialize
- он берет байтовый массив и преобразует его в команду правильного типа . Но вот проблема:
Первый байт буфера будет всегда быть command::id
, связанным с хорошим подклассом. Когда сервер / клиент получит данные, он прочтет идентификатор команды, а затем ему потребуется иметь возможность десериализовать их до правильного типа команды . Вот почему он должен будет вызывать функцию stati c command::unserialize
, а не один из подклассов do_unserialize
.
Быстрое и грязное исправление будет выглядеть как command::unserialize
this:
command* command::unserialize(buffer b) {
auto it{b.begin()};
command::id const id{*it++};
switch(static_cast<command::command_t>(id)) {
case command::command_t::a_command:
return a_command::do_unserialize(it, b.end());
case command::command_t::another_command:
return another_command::do_unserialize(it, b.end());
default:
throw std::invalid_argument("command::unserialize: unknown command ID");
}
}
*** На самом деле не даже, вы не можете иметь виртуальную функцию-член c. Поэтому я понятия не имею, как это можно реализовать.
Даже если это сработало, это не очень весело, потому что это подразумевает необходимость дублировать строку кода для каждой новой созданной команды.
РЕДАКТИРОВАТЬ: Рабочий пример может быть переместить do_unserialize
в конструктор подкласса и вернуть указатель на вновь созданный объект.
class derived_command: public command {
public:
derived_command(buffer::iterator beg, buffer::iterator end) {
// do_unserialize logic
}
};
// unserialize
switch (id) {
case command::command_t::derived_class: return new derived_class(b.begin(), b.end());
}
// ...
Мой вопрос, таким образом, звучит так: есть ли способ динамически связывать новые команды только из поля command::id
? Способ вывести подкласс для использования из его идентификатора? Иначе, мой дизайн имеет недостатки? Есть ли лучший способ сделать то, что я пытаюсь сделать?
Спасибо!