Варианты использования множественного наследования

15

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

Интересно, действительно ли Java (с ее эко-системой) "проста"? Python не сложен и имеет множественное наследование. Итак, не будучи слишком субъективным, мой вопрос ...

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

treecoder
источник
8
Ява пропускает очень много совершенно хороших вещей по очень маленькой причине. Я не ожидал бы достойного оправдания для МИ.
DeadMG
2
Множественное наследование Python - это определенно территория, где можно быть драконами . Тот факт, что он использует разрешение имен слева направо на глубину, имеет существенные проблемы как для удобства сопровождения, так и для понимания. Хотя это может быть полезно в мелких иерархиях классов, в глубоких иерархиях это может быть невероятно нелогичным.
Марк Бут
Я думаю, что причина того, что Java не содержит множественного наследования, заключается в том, что разработчики Java хотели, чтобы их язык был легок в изучении. Многократное наследование, хотя и невероятно мощное в некоторых случаях, трудно понять, и еще труднее использовать для хорошего эффекта; это не то, с чем вы бы хотели столкнуться с начинающим программистом. Я имею в виду: как вы объясните виртуальное наследование тому, кто борется с самой концепцией наследования? И, поскольку множественное наследование также не совсем тривиально на стороне разработчиков, вероятно, Java-разработчик, хотя и опускает его, является беспроигрышным.
cmaster - восстановить монику
Ява номинально типизирована. Python нет. Это делает множественное наследование намного проще для реализации и понимания в Python.
Жюль

Ответы:

11

Плюсы:

  1. Иногда это позволяет более очевидное моделирование проблемы, чем другие способы ее моделирования.
  2. Если разные части имеют ортогональное назначение, это может позволить какую-то композицию

Минусы:

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

В C ++ хорошим примером множественного наследования, используемого для составления ортогональных функций, является случай, когда вы используете CRTP , например, для установки системы компонентов для игры.

Я начал писать пример, но думаю, что пример из реального мира стоит посмотреть. Некоторый код Ogre3D использует множественное наследование приятным и очень интуитивно понятным способом. Например, класс Mesh наследуется от ресурсов и AnimationContainer. Ресурсы предоставляют интерфейс, общий для всех ресурсов, а AnimationContainer - интерфейс, специфичный для управления набором анимаций. Они не связаны между собой, поэтому легко представить себе Mesh как ресурс, который, кроме того, может содержать набор анимаций. Чувствует себя естественно, не так ли?

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

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

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

Klaim
источник
Я был бы очень признателен, если бы вы украсили свой ответ примером проблемы - это сделает такие термины, как «ортогональная цель», более понятными - но спасибо
treecoder
Хорошо, позвольте мне попытаться что-то добавить.
Klaim
Ogre3D - это не то место, где я мог бы искать вдохновение для дизайна. Вы видели их заражение Singleton?
DeadMG
Во-первых, наследник-синглтон на самом деле не синглтон, конструкция и разрушение явные. Далее, Ogre - это слой над аппаратной системой (или графическим драйвером, если вы предпочитаете). Это означает, что должно быть только одно уникальное представление для системных интерфейсов (таких как Root или другие). Они могут удалить синглтон, но здесь дело не в этом. Я добровольно избегал указывать на это, чтобы избежать обсуждения троллей, поэтому, пожалуйста, посмотрите на примеры, которые я указал. Их использование Синглтона может быть не идеальным, но это явно полезно на практике (но только для их системы, а не для всех).
Klaim
4

Существует концепция, называемая миксинами, которая интенсивно используется в более динамичных языках. Множественное наследование - это один из способов, с помощью которого миксины могут поддерживаться языком. Миксины, как правило, используются в классе для накопления различных частей функциональности. Без множественного наследования вы должны использовать агрегацию / делегирование, чтобы получить поведение типа mixin с классом, который немного тяжелее синтаксиса.

RationalGeek
источник
+1 это действительно хорошая причина иметь множественное наследование. Миксины несут дополнительную коннотацию («этот класс не должен использоваться как отдельный»)
ashes999
2

Я думаю, что выбор в основном основан на проблемах, связанных с проблемой алмазов .

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

Я не уверен в значении вашего последнего вопроса. Но если это «в каких случаях полезно множественное наследование?», То во всех случаях, когда вы хотите, чтобы объект A имел функциональные возможности объектов B и C, в основном.

dagnelies
источник
2

Я не буду здесь углубляться в подробности, но вы наверняка сможете понять множественное наследование в python по следующей ссылке http://docs.python.org/release/1.5.1p1/tut/multiple.html :

Единственное правило, необходимое для объяснения семантики, - это правило разрешения, используемое для ссылок на атрибуты класса. Это первая глубина, слева направо. Таким образом, если атрибут не найден в DerivedClassName, он ищется в Base1, затем (рекурсивно) в базовых классах Base1, и только если его там нет, он ищется в Base2 и так далее.

...

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

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

Панкадж Упадхяй
источник
1

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

KeithB
источник
1
Для этого потребуется обобщенное множественное наследование или просто средство, с помощью которого интерфейс может определять поведение по умолчанию для нереализованных методов? Если бы интерфейсы могли указывать реализации по умолчанию только для методов, которые они сами реализуют (в отличие от тех, которые они наследуют от других интерфейсов), такая функция полностью избежала бы проблем двойного ромба, которые затрудняют множественное наследование.
суперкат
1

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

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

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

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

Простой пример (некоторые могут проверить, что переданный дескриптор файла открыт или что-то в этом роде):

// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
    // Pre: x should always be greater than or equal to zero.
    void f(int x) /*final*/
    {
        // Make sure x is actually greater than or equal to zero
        // to meet the necessary pre-conditions of this function.
        assert(x >= 0);

        // Call the overridden function in the subtype.
        f_impl(x);
    }

protected:
    // Overridden by a boatload of subtypes which implement
    // this non-virtual interface.
    virtual void f_impl(int x) = 0;
};

В этом случае, возможно, fвызывается тысячей мест в кодовой базе, а f_implпереопределяется сотней подклассов.

Было бы трудно выполнить такую ​​проверку безопасности во всех 1000 местах, которые вызывают, fили во всех 100 местах, которые переопределяют f_impl.

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

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

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


источник
0

Во-первых: несколько копий базового класса (проблема C ++) и тесная связь между базовыми и производными классами.

Второе: множественное наследование от абстрактных интерфейсов

quant_dev
источник
Вы предлагаете это бесполезно в любом контексте? И что все это может быть разработано / закодировано удобно без этого? Также, пожалуйста, уточните второй пункт.
древовидный код