Наследование против агрегации и "has-a" против "is-a". - PullRequest
5 голосов
/ 04 апреля 2011

В моем коде я считаю полезным использовать миксиноподобное наследование для компоновки объектов с различными блоками.У меня есть:

class Name
{
public:
    typedef int32_t value_type;

public:
    // ctors and dtors
    void set_value(value_type value) { value_ = value; }
    const value_type& value() const { return value_; }

private:
    value_type value_;
};

class NamedObject
{
public:
    void set_name(const Name& name) { name_ = name; }
    const Name& name() const { return name_; }

protected:
    // ctors and dtors

private:
    Name name_;
};

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

class MyObject: public NamedObject, public HasZlevel {
  // functionality that is not connected with NamedObject and HasZLevel
};

Поэтому я решилтрактуйте MyObject как «is-a» NamedObject вместо «has-a» Name. Z-level и Name - это свойства, которые никогда не изменятся в течение времени жизни экземпляра MyObject.Я предпочитаю это агрегации из-за упрощенного использования в алгоритмах, которые определены только для NamedObjects или для объектов, имеющих интерфейс HasZLevel, я могу передать их через NamedObject * или HasZLevel * и быть уверенным, что они не будут удалены или добавлены из-за защищенныхdtors и ctors.

Другой вариант - агрегация:

class MyObject
{
public:
    MyObject(const Name& name): name_(name) {}
    void set_name(const Name& name) { named_->set_name(name) }

    // and so on...

private:
    Name name_;
};

Чтобы использовать это, мне нужен шаблонный алгоритм, который требует от типа параметра иметь функцию-член set_name.

Есть ли здесьесть ли в моем случае веские причины отказаться от своего миксиноподобного дизайна и использовать агрегирование?Может быть, при длительном обслуживании и модификации?

Ответы [ 6 ]

4 голосов
/ 05 апреля 2011

Эмпирическое правило гласит: «Используйте самое слабое отношение, какое только можете».Взгляните на: http://www.gotw.ca/publications/mill06.htm и http://www.gotw.ca/publications/mill07.htm

. Вы можете использовать частное наследование для моделирования "реализуется в срок", но вам следует подумать об исключительной безопасной импликации http://www.gotw.ca/gotw/060.htm

Я знаю, я люблю гуру недели.; -)

РЕДАКТИРОВАТЬ

Я использую этот вид базовых классов для предоставления объектам со свойствами с предопределенной не виртуальной функциональностью

Почему агрегация лучше наследования:

1) Вы можете скрыть методы (или изменить имена) с наследованием, все методы доступны по умолчанию.

2) Вы можете использовать классс не-виртуальным деструктором (std :: string и все контейнеры stl).

3) Вы можете легко изменить реализацию, если вы хотите изменить базовый объект, ваш интерфейс останется нетронутым, однако, если вы наследуете от другогобазовый класс, в котором может измениться ваш интерфейс.

4) Безопасное исключение (см. третью ссылку).

5) Легко читаемое.

Когда необходимо «частное наследование»:

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

2) Когда вы хотите использовать защищенные методы.

Когда "publicнаследование "необходимо:

1) Когда вы хотитедля доступа к закрытым методам и данным (вы не должны этого делать).

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

Я никогда не использовал публичное наследование для моделирования "has-a".

4 голосов
/ 04 апреля 2011

Потому что Множественное наследование - отстой .

Вот почему мне нравятся программисты для клейких лент.Иногда вы работаете в команде и заняты раскруткой кода, и кто-то подходит к вашему столу с кофейной кружкой в ​​руках и начинает рассуждать о том, что, если вы используете многопоточные COM-апартаменты, ваше приложение будетбыть на 34% более блестящим, и это не так сложно, потому что он написал несколько шаблонов, и все, что вам нужно сделать, это умножить наследование от 17 его шаблонов, каждый из которых принимает в среднем 4 аргумента, и вам даже не нужнонаписать тело функции.Это просто гигантский список множественного наследования от разных классов и эй, presto, многопоточный многопоточный COM.И ваши глаза плавают, и вы понятия не имеете, о чем говорит этот фригард, но он просто не уйдет, и даже если он уйдет, он просто вернется в свой кабинет, чтобы написать больше своих умныхклассы, построенные полностью из множественного наследования из шаблонов, без единого тела реализации вообще, и он рухнет как сумасшедший, и вы попадете на страницу ночью, чтобы прийти и попытаться понять это, потому что он будет в некоторомПроклятая встреча «Design Patterns».

И программист с клейкой лентой не боится сказать: «Многократное наследование - отстой.Останови это.Просто остановитесь. ”

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

И да, я только что написал «удушение члена».Многократное наследование действительно так плохо.

1 голос
/ 05 апреля 2011

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

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

Первый вопрос, который вы должны задать: какие детали мне нужно скрыть от моего решения о реализации.

Где вы используете эти грустные предметы? Что они моделируют?

1 голос
/ 05 апреля 2011

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

Миксины приходят из мира Lisp, и я никогда не был уверен, что они так хорошо подходят для C ++. Однако множественное наследование само по себе не является злом и может быть необходимым. Для чего-то очень характерно иметь отношения «есть» с более чем одной вещью.

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

1 голос
/ 04 апреля 2011

Дело в том, что если вы публично наследуете, как здесь, когда-нибудь, где-то, кто-то будет воспринимать ваш класс как NamedObject или HasZlevel и попытаться удалить по родительскому указателю, передать его и т. Д И это, вероятно, просто не сработает.

Если вы хотите использовать подобную стратегию смешанного типа, по крайней мере используйте private наследование, а затем using необходимые родительские методы в открытых разделах соответствующего дочернего класса (классов).

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

0 голосов
/ 05 апреля 2011

Существует способ избежать множественного наследования путем преобразования моего шаблона использования в следующее:

template<class T>
class Named: public T {
  // all for named object
};

template<class T>
struct NamedCompare {
  bool operator<const Named<T>* obj1, const Named<T>* obj2) {
    //...
  }
};

template<class T>
class NamedSet {
  typedef std::set<NamedSet<Named<T>*, NamedCompare> type;
};

bool DoSmthWithFoo(Param1 p1,..., NamedSet<Foo>::type& set);

Таким образом, я могу создавать объекты требуемой функциональности таким образом

typedef Named<Featured<Blabla<Foo> >  > NamedFeaturedBlablaFoo;

или

class NamedFeaturedBlablaFoo: public Named<Featured<Blabla<Foo> >  > {};

, если я могу использовать конструктор по умолчанию для предотвращения загрязнения журналов компилятора.

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

Я вижу одну дыру в дизайне, которую я наследую от конкретных классов, но это нужно объяснить, как это угрожает моему кодуна самом деле.Другая проблема заключается в сокрытии typedefs класса Foo, если это шаблон, и я не вижу способов обойти это.Этот код плохо пахнет ...

Я могу удалить NamedCompare и NamedSet точно так же, но без использования Named, просто вызову name () из T. Это будет работать, и я могу использовать его в автономных алгоритмах.Я думаю, что это намного лучше, чем эти миксины.

...