C++ Дизайн и полиморфизм

У меня общий вопрос по дизайну классов C++. Например, мне нужен генератор пакетов. Итак, учитывая тип пакета, я должен сгенерировать пакет этого типа. Итак, у меня есть базовый класс генератора пакетов.

Подход 1:

class BasePacketGenerator {
public:
    virtual Packet* generatePacket (int type); // implements the generic case
                                               // hence not pure virtual
protected:
    //
    // Common functionality for all types
    // 
   virtual void massagePacket (Packet& pkt); // called by generatePacket
                                           // but needs special handling for 
                                           // some types
   virtual void calculateCheckSum(Packet& pkt); // called by generate packet
   ...
};

Производные классы для обработки каждого типа:

class Type1PacketGenerator : public BasePacketGenerator {
public:
    // Dont have to override any base class implementationa
protected:
    void calculateCheckSum(Packet& pkt) override;
};

class Type2PacketGenerator : public BasePacketGenerator {
public:
    Packet* generatePacket(int type) override;
};

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

Подход 2:

class TypeHandler {

    virtual Packet* setupPacket();
    virtual void calculateCheckSum(Packet& pkt);
    virtual void setupFields (Packet& pkt);
}

class PacketGenerator {
public:

    TypeHandler *handler_; // lets say this is setup based on the type

    Packet* generatorPacket(int type)
    {
       auto packet = handler_->setupPacket();
       handler_->massagePacket();
       handler_->calculateCheckSum(*packet);
       return packet;
    }
}

Есть ли какое-то преимущество в использовании того или иного подхода?

Подход 1 более гибкий, поскольку он не должен следовать одному и тому же образу действий. Например: если Type2Generator не нуждается в контрольной сумме, ему даже не нужно вызывать calculateCheckSum () или, если ему нужны дополнительные функции, нам не нужно добавлять его ко всем генераторам типов.

Но подход 2 более читабелен, поскольку все типы делают то же самое.

Похоже на заводскую выкройку?

SergeyA 16.05.2018 20:57

Я считаю, что ваш второй пример касается темы разработки на основе политик - en.wikipedia.org/wiki/Policy-based_design. Хорошо сочетается с шаблонами.

audio 16.05.2018 21:12

Ответ на ваш вопрос очень зависит от разных факторов. Дизайн класса должен учитывать многие аспекты предметной области и проблемы. Например, почему вы не можете реализовать «простейший» дизайн полиморфизма? Пример здесь. Интерфейс packet и разные реализации для каждого типа.

BiagioF 16.05.2018 22:23

@BiagioFesta: это похоже на мой подход 2, за исключением того, что он использует чистый класс интерфейса. Я попытаюсь понять, смогу ли я создать интерфейсный класс и как бы абстрагировать общие функции для другого класса.

MGH 19.05.2018 00:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
4
215
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Чувствительность современного дизайна склонна к очень агрессивному разделению проблем. Таким образом, это приводит к предпочтению базовых классов, которые представляют собой не что иное, как объявления чистых виртуальных функций.

Каждый класс отвечает за 1 вещь.

Таким образом, ваш второй подход считается гораздо более предпочтительным. Основная причина (и единственная, которая вам действительно нужна) заключается в том, что это значительно упрощает написание надлежащих модульных тестов.

Редактировать: заметьте, это все еще выглядит немного неуклюже, но это больше вопрос Обмен стека codereview, где вы можете получить подробный отзыв о своей структуре.

Спасибо за ответ. по поводу base classes that are nothing but pure virtual function declarations. Этого можно достичь, если я перенесу все общие функции в другой класс, и каждый обработчик типов просто будет ссылаться на него, я думаю.

MGH 16.05.2018 21:28

Другие вопросы по теме