2015-04-28 1 views
1

В настоящее время у меня есть следующие классы управления различными видами переменных:Я в конкретном случае оправдываю многократное наследование?

class Variable; 
class Number : public Variable; 
class Boolean : public Variable; 
class RealNumber : public Number; 
class IntegerNumber : public Number; 

Это классическое дерево наследования, и он работает хорошо. Я управляю векторами Number* экземпляров и векторов Boolean* экземпляров.

Я хотел бы добавить другой тип переменной: «виртуальная переменная» с определенным поведением, но с аналогичным интерфейсом. Затем я хочу работать с векторами «виртуальных чисел» и векторами «виртуальных булевых».

Первое решение добавляет флаг в Variable, но я предпочитаю защиту компиляции и управляю std::vector<VirtualResponse*> и std::vector<VirtualBoolean*>, чтобы гарантировать, что переменные являются виртуальными в векторе.

Тогда, я думаю, может быть, я в конкретном случае, когда множественное наследование оправдано, но я новичок в множественном наследовании. Что вы думаете об этом классе?

class VirtualVariable : public virtual Variable; 
class VirtualNumber : public virtual Number, public virtual VirtualVariable; 
class VirtualBoolean : public virtual Boolean, public virtual VirtualVariable; 
class VirtualRealNumber : public virtual RealNumber, public virtual VirtualNumber; 
class VirtualIntegerNumber : public virtual IntegerNumber, public virtual VirtualVariable; 

Это классический способ сделать это? Может быть, это ловушка? Я злоупотребляю ключевым словом virtual?

EDIT: пример того, что я хочу сделать:

void my_function(const std::vector<SpecItem>& spec) 
{ 
    // First : create the description from the spec 
    std::vector<Number*> descr; 
    std::vector<VirtualNumber*> virt_descr; 
    for(unsigned long int i = 0; i < spec.size(); ++i) 
    { 
    if(spec[i].isNumber()) 
     if(spec[i].isReal()) 
     { 
     double lower_bound = spec[i].lowerBound(); 
     double upper_bound = spec[i].upperBound(); 
     if(spec[i].isVirtual()) 
     { 
      std::string formula = spec[i].formula(); 
      virt_descr.push_back(new VirtualRealNumber(lower_bound,upper_bound,formula)); 
     } 
     else 
      descr.push_back(new RealNumber(lower_bound,upper_bound)); 
     } 
     else if(spec[i].isInteger()) 
     { 
     long int lower_bound = ceil(spec[i].lowerBound()); 
     long int upper_bound = floor(spec[i].upperBound()); 
     if(spec[i].isVirtual()) 
     { 
      std::string formula = spec[i].formula(); 
      virt_descr.push_back(new VirtualIntegerNumber(lower_bound,upper_bound,formula)); 
     } 
     else 
      descr.push_back(new IntegerNumber(lower_bound,upper_bound)); 
     } 
    } 
    // Now, descr is a vector of Number*, virt_descr is a vector of VirtualNumber* 

    // Second step : assign values to the numbers 
    std::vector<double> values; 
    for(unsigned long int i = 0; i < descr.size(); ++i) 
    { 
    double new_value = (descr[i]->lowerBound() + descr[i]->upperBound())/2.0; 
    values.push_back(descr[i]->adjust(new_value)); 
    } 

    // Third step : evaluate virtual numbers 
    std::vector<double> virt_values; 
    for(unsigned long int i = 0; i < virt_descr.size(); ++i) 
    { 
    double new_value = virt_descr[i]->evaluate(values); 
    values.push_back(descr[i]->adjust(new_value)); 
    } 

    return 0; 
} 
+2

Я рекомендую вам ознакомиться с этим [примером нескольких наследований] (https://isocpp.org/wiki/faq/multiple-inheritance#mi-example) (в разделе * Можете ли вы привести пример, демонстрирующий вышеприведенное руководящие принципы? *) и посмотрите, может ли какой-либо из альтернатив работать для вас. –

+5

Мое мнение состоит в том, что наследование (множественное или иное, виртуальное или иное) не может быть правильно спроектировано, не понимая сначала * цели *. Написание наследования или объектно-ориентированного дизайна * не может быть целью. Так в чем же цель? Чего вы пытаетесь достичь? – Nawaz

+0

Управляйте объектами 'std :: vector ' и 'std :: vector ' так же, как я в настоящее время использую 'std :: vector ' с некоторыми алгоритмами. «VirtualVariables» имеет только определенное поведение при оценке, но сохраняет то же поведение для математической точки зрения. – Caduchon

ответ

1

Множественное наследование означает в пользу чистый дизайн, обеспечивая separation of concerns. Так что это может быть совершенно оправдано.

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

Она начинается на линии:

class VirtualVariable : public virtual Variable; 

1) Есть ли у вас нужно виртуальное наследование?

Множественное наследование действительно просто и весело, пока вы не придете к виртуальному наследованию. Это означает, что для класса, наследующего несколько раз от VirtualVariable, все экземпляры под-объектов VirtualVariable будут иметь общий базовый объект Variable. Для получения дополнительной информации, читайте на diamond problem.

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

Это виртуальное наследование потребует, чтобы для каждого конструктора вы явно инициализировали свою виртуальную базу.

2) Какова реальная связь между Variable и VirtualVariable?

Согласно вашему объяснению, я понял, что концепция «Виртуальности» не зависит от концепции «Переменная». С другой стороны, в вашем коде вы делаете вид, что VirtualVariableявляетсяVariable.

Если это что-то независимое, вы должны сохранять его независимым. Это будет лучше разделение проблем. Хорошая новость заключается в том, что вы после этого больше не требуется виртуальное наследование:

class Virtuality;  
class VirtualVariable : public Variable, public Virtuality; 
class VirtualNumber : public Number, public Virtuality; 
class VirtualBoolean : public Boolean, public Virtuality; 
class VirtualRealNumber : public RealNumber, public Virtuality; 
class VirtualIntegerNumber : public IntegerNumber, public Virtuality; 

3) Вы смотрели на шаблонах?

Вы сказали:

Первое решение добавляет флаг в переменной, но я предпочитаю безопасность компиляции и управления

Если это только для проверки типов компиляции, вы можете использовать шаблоны:

template <bool isvirtual> 
Variable { ...}; 
template <bool isvirtual> 
class Number : Variable<isvirtual> { ...}; 
... 
std::vector<Number<true>*> v; 

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

EDIT: для редактирования

Solution 2 и 3 должны полностью быть в состоянии осуществить то, что вы собираетесь делать.

Я разместил здесь online demo решения 2 с полиморфизмом, чтобы продемонстрировать, насколько легко вы могли бы смешивать «виртуальные» и «виртуальные» элементы. Она определяет функцию IntegerNumber add (IntegerNumber &a, IntegerNumber&b);, и использует его в этом сценарии:

IntegerNumber n(27); // number with an inital value 
VirtualIntegerNumber k; // value will be calculated from some formula 
add (n, k);   // ok, my demo just displays the value of each parameter ;-) 

* Кстати, я думаю, что конструкции if (isxxx())... if (isyyy()) ... в вашем коде настоятельно рекомендуем вам следует использовать полиморфизм.

+0

Thx для вашего ответа. Я хочу иметь возможность манипулировать указателями «Number *», которые являются «IntegerNumber *» или «RealNumber *», но я также хочу манипулировать указателями VirtualNumber *, которые являются «VirtualIntegerNumber *» или «VirtualRealNumber *» на самом деле , Думайте, что это невозможно с вашими решениями 2) и 3), правильно? – Caduchon

+0

Думаю, что так и должно. Можете ли вы изменить свой вопрос, показывая пример того, что, по вашему мнению, работает с вашим подходом и не работает с 2 и 3, чтобы я мог уточнить свой ответ, если это необходимо? – Christophe

+0

Я отредактировал мой вопрос. ;-) – Caduchon

-1

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

class Number : public Variable 
{ 
    ... 
    protected: 
     virtual void functionWithSpecialUsage() { // Empty in base class, used by inheritance only }; 
} 

class VirtualNumber : public Number 
{ 
    ... 
    protected: 
     void functionWithSpecialUsage() { // Add class specific behavior here }; 
} 
+0

У меня есть две проблемы с этой иерархией. 1/Я должен переписать виртуальную функциональность в каждом подклассе. 2/Если я определяю 'class VirtualRealNumber: public RealNumber', я не могу управлять вектором' VirtualNumber * '. – Caduchon

+0

для итерации по набору классов вам нужно выбрать базовый класс для них. в вашем случае std :: vector . Вы можете использовать их для динамических или статических бросков. – norca

+0

Нет. Мне нужно иметь указатель на мои классы с уровня 2. «Переменная» - это всего лишь общий тип, чтобы избежать дублирования кода. Действительно, я реализовал такие функции, как 'cloneAsNumber() const' в моих подклассах уровня 2. Динамическое приведение к каждому доступу к переменной действительно слишком медленно для того, что я делаю. – Caduchon