Разработка шаблона для оператора рефакторинга - PullRequest
6 голосов
/ 04 августа 2010

У меня есть что-то вроде следующего в заголовке

class MsgBase
{
  public:
    unsigned int getMsgType() const { return type_; }
    ...
  private:
    enum Types { MSG_DERIVED_1, MSG_DERIVED_2, ... MSG_DERIVED_N };
    unsigned int type_;
    ...
};

class MsgDerived1 : public MsgBase { ... };
class MsgDerived2 : public MsgBase { ... };
...
class MsgDerivedN : public MsgBase { ... };

и используется как

MsgBase msgHeader;
// peeks into the input stream to grab the
// base class that has the derived message type
// non-destructively
inputStream.deserializePeek( msgHeader ); 
unsigned int msgType = msgHeader.getMsgType();

MsgDerived1 msgDerived1;
MsgDerived2 msgDerived2;
...
MsgDerivedN msgDerivedN;

switch( msgType )
{
  case MSG_DERIVED_1:
    // fills out msgDerived1 from the inputStream
    // destructively
    inputStream.deserialize( msgDerived1 );
    /* do MsgDerived1 processing */
    break;
  case MSG_DERIVED_2:
    inputStream.deserialize( msgDerived2 );
    /* do MsgDerived1 processing */
    break;
  ...
  case MSG_DERIVED_N:
    inputStream.deserialize( msgDerivedN );
    /* do MsgDerived1 processing */
    break;
}

Это похоже на тип ситуации, который был бы довольно распространенным и хорошо подходит длярефакторинг.Каков наилучший способ применения шаблонов проектирования (или редизайна базовых функций языка C ++) для рефакторинга этого кода?

Я читал, что шаблон Command обычно используется для рефакторинга операторов switch, но это применимо только при выборемежду алгоритмами, чтобы сделать задачу.Это место, где применяется фабричный или абстрактный фабричный шаблон (я тоже не очень знаком с ним)?Двойная рассылка?

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

Ответы [ 4 ]

5 голосов
/ 04 августа 2010

Можно использовать шаблон Factory Method , который создает правильную реализацию базового класса (производного класса) на основе значения, которое вы просматриваете из потока.

3 голосов
/ 04 августа 2010

Переключатель не так уж и плох. Это один из способов реализовать фабричный шаблон. Он легко тестируется, позволяет легко понять весь диапазон доступных объектов и хорош для тестирования покрытия.

Другой метод заключается в построении отображения между типами перечислений и фабриками для создания определенных объектов из потока данных. Это превращает переключатель времени компиляции в поиск во время выполнения. Отображение может быть построено во время выполнения, что позволяет добавлять новые типы без перекомпиляции всего.

// You'll have multiple Factories, all using this signature.
typedef MsgBase *(*Factory)(StreamType &);

// For example:
MsgBase *CreateDerived1(StreamType &inputStream) {
    MsgDerived1 *ptr = new MsgDerived1;
    inputStream.deserialize(ptr);
    return ptr;
}

std::map<Types, Factory> knownTypes;
knownTypes[MSG_DERIVED_1] = CreateDerived1;

// Then, given the type, you can instantiate the correct object:
MsgBase *object = (*knownTypes[type])(inputStream);

...

delete object;
2 голосов
/ 04 августа 2010

Извлеките типы и type_ из MsgBase, они там не принадлежат.

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

class DerivedMessage : public Message
{
public:
   static Message* Create(Stream&);
   bool Serialize(Stream&);

private:
   static bool isRegistered;
};

// sure, turn this into a macro, use a singleton, whatever you like
bool DerivedMessage::isRegistered =
      g_messageFactory.Register(Hash("DerivedMessage"), DerivedMessage::Create);

и т.д.. Статический метод Create выделяет новое DerivedMessage и десериализует его, метод Serialize записывает токен (в данном случае Hash("DerivedMessage")) и затем сериализует себя. Один из них, вероятно, должен проверить isRegistered, чтобы он не был полностью удален компоновщиком.

(Примечательно, что этот метод не требует перечисления или другого «статического списка всего, что может когда-либо существовать». В настоящее время я не могу придумать другой метод, который в некоторой степени не требует циклических ссылок)

1 голос
/ 04 августа 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...