Я работаю над проектом сервера, который реализует проприетарный протокол. Сервер реализован с фабричным шаблоном на C++, и теперь мы сталкиваемся с проблемой понижения.
Протокол, над которым я работаю, предназначен для автоматического управления медленными сетями, такими как RS485, ZigBee, узкополосный ПЛК и т. д. Мы спроектировали основной сервер с заводским шаблоном. При получении нового фрейма мы сначала идентифицируем связанный с этим фреймом тип устройства, вызывая фабричный метод для создания нового экземпляра «парсера», и отправляем кадр в экземпляр парсера.
Наш проприетарный протокол реализован в чистом двоичном формате, вся информация, которая нам может понадобиться, записывается в сам кадр, поэтому базовый интерфейс можно определить как можно проще. Мы также реализуем подход автоматической регистрации для нашей фабрики (подробный код, связанный с операцией std::map, здесь опущен):
// This is our "interface" base-class
class parser
{
public:
virtual int parse(unsigned char *) = 0;
virtual ~parser() { }
};
// The next two classes are used for factory pattern
class instance_generator
{
public:
virtual parser *generate() = 0;
};
class parser_factory
{
private:
static std::map<int,instance_generator*> classDB;
public:
static void add(int id, instance_generator &genrator);
parser *get_instance(int id);
};
// the two template classes are implementations of "auto-regisrtation"
template <class G, int ID> class real_generator : public instance_generator
{
public:
real_generator() { parser_factory::add(ID,this); }
parser *generate() { return new G; }
};
template <class T, int N> class auto_reg : virtual public parser
{
private:
static real_generator<T,N> instance;
public:
auto_reg() { instance; }
};
template <class T, int N> parser_generator<T,N> auto_reg<T,N>::instance;
// And these are real parser implementations for each device type
class light_sensor : public auto_reg<light_sensor,1>
{
public:
int parse(unsigned char *str)
{
/* do something here */
}
};
class power_breaker : public auto_reg<power_breaker,2>
{
public:
int parse(unsigned char *str)
{
/* do something here */
}
};
/* other device parser */
Этот фабричный шаблон работал очень хорошо, и новые типы устройств легко использовать.
Однако в последнее время мы пытаемся взаимодействовать с существующей системой управления, которая обеспечивает аналогичную функциональность. Целевая система довольно старая и предоставляет только последовательный интерфейс на основе ASCII, похожий на AT-команду. Нам удалось решить проблему связи с PTY, но теперь нужно решить проблему с реализацией парсера.
Командный интерфейс целевой системы весьма ограничен. Я не могу просто ждать и слушать, что приходит, я должен опрашивать состояние, и я должен опрашивать дважды — первый опрос для заголовка и второй опрос для полезной нагрузки — чтобы получить полную команду. Это проблема для нашей реализации, потому что мне нужно передать ДВА кадра в экземпляр парсера, чтобы он мог работать:
class legacy_parser : virtual public parser
{
public:
legacy_parser() { }
int parse(unsigned char *str)
{
/* CAN NOT DO ANYTHING WITHOUT COMPLETE FRAMES */
}
virtual int parse(unsigned char *header, unsigned char *payload) = 0;
};
class legacy_IR_sensor :
public legacy_parser,
public auto_reg<legacy_IR_sensor,20>
{
public:
legacy_IR_sensor(){ }
int parse(unsigned char *header, unsigned char *payload)
{
/* Now we can finally parse the complete frame */
}
};
Другими словами, нам нужно будет вызвать метод производного класса, а метод не определен в базовом классе. И мы используем фабричный шаблон для создания экземпляра производного класса.
Теперь у нас есть несколько вариантов:
Просто объединить две строки в одну не получится. Обе строки содержат некоторую информацию об устройстве, и они должны анализироваться отдельно. Если мы выберем этот подход, мы выполним некоторый «предварительный разбор» экземпляра синтаксического анализатора, прежде чем сможем объединить строку. И мы не думаем, что это хорошая идея.
Понижение возврата parser_factory::get_instance() до legacy_parser.
Создайте еще одну независимую фабрику, которая содержит только классы, производные от legacy_parser.
Измените определение instance_generator и parser_factory, чтобы они также могли генерировать (legacy_parser*), не затрагивая при этом весь существующий код:
class instance_generator { public: virtual parser *generate() = 0; virtual legacy_parser *generate_legacy() { return NULL; } }; class extended_parser_factory : public parser_factory { public: legacy_parser *get_legacy_instance(int id); };
Реализовать «умный указатель» с шаблоном посетителя для обработки экземпляров, полученных из legacy_parser:
class smart_ptr { public: virtual void set(parser *p) = 0; virtual void set(legacy_parser *p) = 0; }; class parser { public: parser() { } virtual int parse(unsigned char *) = 0; virtual void copy_ptr(smart_ptr &sp) // implement "Visitor" pattern { sp.set(this); } virtual ~parser() { } }; class legacy_parser : virtual public parser { public: legacy_parser() { } void copy_ptr(smart_ptr &sp) // implement "Visitor" pattern { sp.set(this); } int parse(unsigned char *str) { /* CAN NOT DO ANYTHING WITHOUT COMPLETE FRAMES */ } virtual int parse(unsigned char *header, unsigned char *payload) = 0; }; class legacy_ptr : public smart_ptr { private: parser *miss; legacy_parser *hit; public: legacy_ptr& operator=(parser *rhv) { rhv->copy_ptr(*this); return *this; } void set(parser* ptr) { miss=ptr; /* ERROR! Do some log or throw exception */ } void set(legacy_parser *ptr) { hit = ptr; } legacy_parser& operator*() { return *hit; } ~legacy_ptr() { if(miss) { delete miss; } if(hit) { delete hit; } } };
Очевидно, что Downcasting с dynamic_cast‹> — это самый простой подход для нас, но никому из нас не нравится эта идея, потому что мы все чувствуем, что «Downcast» — это «зло». Однако никто не может точно объяснить, почему это «зло».
Прежде чем мы примем решение, я хотел бы услышать больше комментариев об этих вариантах.