Я работаю над проектом сервера, который реализует собственный протокол. Сервер реализован с фабричным шаблоном в 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 */
Этот фабричный шаблон работал очень хорошо, и новые типы устройств легко тратить.
Однако в последнее время мы пытаемся взаимодействовать с существующей системой управления, которая обеспечивает аналогичную функциональность. Целевая система довольно старая и предоставляет только последовательный интерфейс, подобный AT-командам, на основе ASCII. Нам удалось решить проблему связи с 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);
};
Реализация «умного указателя» с шаблоном Visitor для обработки экземпляров, полученных из 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 <> - это самый простой подход для нас, но никому из нас не нравится эта идея, потому что мы все чувствуем, что это «зло» - что-то унижать. Однако никто не может точно объяснить, почему это "зло".
Прежде чем мы примем решение, я хотел бы услышать больше комментариев об этих опциях.