Должен ли я избегать удушения при использовании заводского шаблона? - PullRequest
4 голосов
/ 22 мая 2011

Я работаю над проектом сервера, который реализует собственный протокол. Сервер реализован с фабричным шаблоном в 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 */
    }
};

Другими словами, нам нужно вызвать метод производного класса, а метод не определен в базовом классе. И мы используем шаблон фабрики для генерации экземпляра производного класса.

Теперь у нас есть несколько вариантов:

  1. Просто объединить две строки в одну не работает. Обе строки содержат некоторую указанную устройством информацию, и они должны анализироваться отдельно. Если мы воспользуемся этим подходом, мы выполним некоторый «предварительный анализ» из экземпляра синтаксического анализатора, прежде чем сможем объединить строку. И мы не думаем, что это хорошая идея.

  2. Понизить возврат parser_factory :: get_instance () к legacy_parser.

  3. Создайте еще одну независимую фабрику, которая содержит только классы, полученные из legacy_parser.

  4. Измените определения 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);
    };
    
  5. Реализация «умного указателя» с шаблоном 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 <> - это самый простой подход для нас, но никому из нас не нравится эта идея, потому что мы все чувствуем, что это «зло» - что-то унижать. Однако никто не может точно объяснить, почему это "зло".

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

Ответы [ 3 ]

2 голосов
/ 22 мая 2011

http://en.wikipedia.org/wiki/Circle-ellipse_problem ваш первый пример зла.Если вы видите, что можете что-то сделать с нарушением базовых принципов, то вам нужно изобрести другое колесо или попробовать другую шляпу: http://en.wikipedia.org/wiki/Six_Thinking_Hats

0 голосов
/ 23 мая 2011

Проблема в том, что legacy_parser ожидает два кадра, а не один в вашем исходном парсере. Таким образом, возможное решение состоит в том, чтобы немного изменить исходный синтаксический анализатор и заставить его работать с более чем одним кадром. Например, parse может вернуть предопределенную константу, если парсер хочет больше кадров, и тогда legacy_parser может быть реализован так:

class legacy_parser : public parser {
 public:
  int parse(unsigned char *str) {
    if (parse_header_) {
      // store str in header_
      parse_header_ = false;
      return kExpectMoreFrames;
    } else {
      return parse(header_, str);
    }
  }
 private:
  int parse(unsigned char *header, unsigned char *parload) {
    // ...
  }
  bool parse_header_;
  unsigned char *header_;
};

На существующий код синтаксического анализатора не следует влиять, если они случайно не используют значение, определенное для значения «more frames».

0 голосов
/ 22 мая 2011

Понижение рейтинга, особенно в реализации фабричного шаблона, имеет для меня довольно хороший смысл.Это на самом деле хорошо сочетается с идеологией «программа к интерфейсу».Не уверен, почему люди считают, что уныние - это плохо.Проверьте Контравариантность , поскольку это то, на что вы смотрите.

...