2016-06-11 1 views
4

у меня есть следующий абстрактный базовый класс:Наследование класса с "неуместных" методы

class DLAContainer { 
public: 
    DLAContainer() { std::random_device rd; mt_eng = std::mt19937(rd()); } 
    virtual void generate(std::size_t _n) = 0; 
protected: 
    std::mt19937 mt_eng; 

    virtual void spawn_particle(int& _x, int& _y, 
     std::uniform_real_distribution<>& _dist) = 0; 
    virtual void spawn_particle(int& _x, int& _y, int& _z, 
     std::uniform_real_distribution<>& _dist) = 0; 

    // ... among other methods to be overridden... 
}; 

и два класса, которые наследуют от DLAContainer:

class DLA_2d : public DLAContainer { 
public: 
    DLA_2d() : DLAContainer() { // initialise stuff } 
    void generate(std::size_t _n) { // do stuff } 
private:; 
    std::queue<std::pair<int,int>> batch_queue; 
    // ... 

    void spawn_particle(int& _x, int& _y, 
     std::uniform_real_distribution<>& _dist) { // do stuff } 
    void spawn_particle(int& _x, int& _y, int& _z, 
     std::uniform_real_distribution<>& _dist) { // do nothing } 

    //... 
}; 

и

class DLA_3d : public DLAContainer { 
public: 
    DLA_3d() : DLAContainer() { // initialise stuff } 
    void generate(std::size_t _n) { // do stuff } 
private:; 
    std::queue<std::tuple<int,int,int>> batch_queue; 
    // ... 

    void spawn_particle(int& _x, int& _y, 
     std::uniform_real_distribution<>& _dist) { // do nothing } 
    void spawn_particle(int& _x, int& _y, int& _z, 
     std::uniform_real_distribution<>& _dist) { // do stuff } 

    //... 
}; 

Как вы можете видеть, есть две перегрузки spawn_particle - одна для двумерной решетки, а другая для 3D, однако оба являются чистыми virtual функциями и поэтому должны быть переопределены/реализованы как в подклассах DLA_2d, так и в DLA_3d, где 3D-метод ничего не сделает в DLA_2d и наоборот для DLA_3d.

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

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

Редактировать: Я должен добавить, что DLAContainer имеет несколько других методов (и полей). Некоторые из этих методов определены (чтобы они могли использоваться как DLA_2d, так и DLA_3d), а другие являются чисто виртуальными, аналогичными spawn_particle. Вот почему у меня есть DLAContainer как абстрактный базовый класс в этом случае.

+0

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

+0

Я пропустил справедливую сумму фактического кода от 'DLAContainer' - там больше, и в идеале это должен быть абстрактный класс. Интересная идея с исключением определения бросания в базовом классе, которая может быть приятнее - плюс «DLAContainer», по-прежнему будет абстрактной в этом случае, поскольку «generate (std :: size_t)» останется чистым-виртуальным, поскольку подпись эквивалентна для «DLA_2d» 'и' DLA_3d'. – ArchbishopOfBanterbury

+3

Вам нужно знать тип объекта перед вызовом любой из ваших функций 'spawn_particle()', поэтому нет никакой пользы от их присутствия в базовом классе вообще. Вы также можете быть объявлены каждый в своем соответствующем подклассе. – Galik

ответ

2

Вы правы, это неуклюжие.
И это результат общей ошибки в дизайне OO: Использование наследования просто для избежания дублирования кода, если подтип нельзя назвать IS A родительский тип.

В настоящее время вы сможете позвонить:

DLA_3d d3; 
d3.spawn_particle(...) //The 2D version 
//and 
DLA_2d d2; 
d2.spawn_particle(...) //The 3D version 

, казалось бы, никаких «негативных последствий», игнорируя призыв и ничего не делать. Проблема заключается в том, что код, вызывающий spawn_particle, должен знать, что:

  • Вызов метода ничего не может сделать.
  • Или предварительно проверьте тип, чтобы узнать, какой метод вызывать.

Оба они налагают ненужное дополнительное знание/работу на вызывающего абонента. И эффективно сделать его более склонным к ошибкам использовать.

PS: Обратите внимание, что ошибка при запуске не исправляет дизайн. Поскольку абоненты теперь осталось: «Вызов метода может бросить или предварительно проверить тип ...»


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

  • Сначала в первую очередь рассмотрим следующий принцип дизайна: благосклонности состава по наследству.
    • Вам не нужно наследовать код повторного использования: вы можете содержать/ссылаться на экземпляр другого объекта и выгружать работу, вызывая содержащийся объект.
    • Вы упомянули, что DLAContainer имеет ряд других полей и методов. Сколько из них можно перенести на другой или несколько классов?
  • Действительно ли имеет смысл, чтобы контейнер был нерестующим? Ответственность контейнера должна заключаться в том, чтобы держать вещи. Вы следуете принципу единственной ответственности ? (Сомневаюсь).
  • Рассмотрите возможность перемещения каждого метода spawn_particle в соответствующий подкласс. (Хотя я подозреваю, что это оставит вас с очень похожими проблемами.)
  • Разработка абстракции для «частицы». Тогда оба «породителя частиц» могут иметь одну и ту же подпись, но порождают разные конкретные экземпляры «частицы» I.e. 2D-частицу или трехмерную частицу.
2

Ваша основная проблема заключается в несогласованности интерфейса, объявленного абстрактным базовым классом. Предполагается ли это, что это 2D или 3D-интерфейс? То, как вы это объявили, это и то, и другое, но производные классы тоже и, следовательно, не полностью реализуют интерфейс: это вызывает все ваши проблемы.

Подумайте, как вы собираетесь использовать базовый класс, возможно, вы можете сделать его шаблоном по количеству измерений? Если существует более virtual методов, имеющих размерность, они могут быть в основании, например.

struct DLAContainerBase 
{ 
    /* some basic virtual interface */ 
}; 

template<int Dims> 
struct DLAContainer : DLAContainerBase 
{ 
    virtual void spawn_particle(std::array<int,Dims>&, 
           std::uniform_real_distribution<>&) = 0; 
}; 

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