Чем плохо множественное наследование

Обновлено: 30.06.2024

Что с множественным наследованием? Есть конкретные образцы?

Я бы просто упомянул, что C ++ отлично подходит для того, чтобы дать вам достаточно веревки, чтобы повеситься.

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

Самая очевидная проблема связана с переопределением функции.

Скажем, есть два класса A и B , каждый из которых определяет метод doSomething . Теперь можно определить третий класс C , который наследует от обоих A и B , но вы не переопределить doSomething метод.

Когда компилятор заполняет этот код .

. какую реализацию метода следует использовать? Без дополнительных разъяснений компилятор не может разрешить двусмысленность.

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

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

Множественное наследование очень усложняет задачу.

Если класс C наследуется от обоих A и B , компилятор должен решить, размещать ли данные по AB порядку или по BA порядку.

Но теперь представьте, что вы вызываете методы B объекта. Неужели это просто B ? Или это действительно C объект, вызываемый полиморфно через его B интерфейс? В зависимости от фактической идентичности объекта физическая структура будет отличаться, и невозможно узнать смещение функции, вызываемой на сайте вызова.

Проблемы, о которых вы упомянули, на самом деле не так уж и сложно решить. На самом деле, например, Эйфель прекрасно это делает! (и без введения произвольного выбора или чего-то еще)

Например, если вы наследуете от A и B, оба из которых имеют метод foo (), тогда, конечно, вам не нужен произвольный выбор в вашем классе C, унаследованный от обоих A и B. Вам нужно либо переопределить foo, чтобы было ясно, что будет используется, если вызывается c.foo () или в противном случае вам нужно переименовать один из методов в C. (он может стать bar ())

Также я считаю, что множественное наследование часто бывает весьма полезным. Если вы посмотрите библиотеки Eiffel, вы увидите, что он используется повсеместно, и лично я пропустил эту функцию, когда мне пришлось вернуться к программированию на Java.

Я согласен. Основная причина, по которой люди ненавидят MI, та же, что и в случае с JavaScript или статической типизацией: большинство людей когда-либо использовали только очень плохие его реализации - или использовали их очень плохо. Судить MI по C ++ - все равно что судить OOP по PHP или автомобили по Pintos.

@Guvante единственная проблема с MI на любом языке - это дерьмовые программисты, которые думают, что могут прочитать учебник и внезапно узнать язык.

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

двусмысленность, которая возникает, когда два класса B и C наследуются от A, а класс D наследуется от B и C. Если в A есть метод, который B и C переопределили , и D не переопределяет его, то какая версия метод, который наследует D: метод B или метод C?

. Это называется "алмазной проблемой" из-за формы диаграммы наследования классов в этой ситуации. В этом случае класс A находится наверху, B и C отдельно под ним, а D соединяет их вместе внизу, образуя ромбовидную форму .

который имеет решение, известное как виртуальное наследование. Проблема только в том, что вы делаете это неправильно.

@IanGoldby: Виртуальное наследование - это механизм для решения части проблемы, если нет необходимости разрешать повышающие и понижающие преобразования с сохранением идентичности среди всех типов, от которых произошел экземпляр или для которых он является заменяемым . Учитывая X: B; Y: В; и Z: X, Y; предположим, что someZ является экземпляром Z. При виртуальном наследовании (B) (X) someZ и (B) (Y) someZ являются разными объектами; учитывая любой из них, один может получить другой с помощью понижающего и восходящего преобразования, но что, если у одного есть a someZ и он хочет привести его к, Object а затем к B ? Что B он получит?

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

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

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

допустим, у вас есть объекты A и B, которые наследуются C. A и B оба реализуют foo (), а C - нет. Я вызываю C.foo (). Какая реализация будет выбрана? Есть и другие проблемы, но такие вещи очень серьезные.

Но это не совсем конкретный пример. Если и у A, и у B есть функция, весьма вероятно, что C также потребуется собственная реализация. В противном случае он все еще может вызывать A :: foo () в своей собственной функции foo ().

@Quantum: А что, если этого не произойдет? Легко увидеть проблему с одним уровнем наследования, но если у вас много уровней и у вас есть случайная функция, которая где-то вдвое больше, это становится очень сложной проблемой.

Кроме того, дело не в том, что вы не можете вызвать метод A или B, указав, какой из них вы хотите, дело в том, что если вы не укажете, тогда нет хорошего способа выбрать один. Я не уверен, как C ++ справляется с этим, но если кто-то знает, можно ли упомянуть об этом?

@tloach - если C не разрешает двусмысленность, компилятор может обнаружить эту ошибку и вернуть ошибку времени компиляции.

@Earmon - из-за полиморфизма, если foo () виртуальный, компилятор может даже не знать во время компиляции, что это будет проблемой.

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

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

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

Существуют и другие методы, такие как миксины, которые решают те же проблемы и не имеют проблем, присущих множественному наследованию.

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

Я не думаю, что проблема с бриллиантами - это проблема, я считаю эту софизму, и ничего больше.

Наихудшая проблема, с моей точки зрения, с множественным наследованием - это жертвы RAD и люди, которые утверждают, что они разработчики, но на самом деле застряли на полузнании (в лучшем случае).

Лично я был бы очень рад, если бы наконец смог сделать что-то подобное в Windows Forms (это неправильный код, но он должен дать вам идею):

На мой взгляд, в ЛЮБОМ повторении кода на современном языке не должно быть абсолютно никакой необходимости, ни в малейшей степени.

Я склонен согласиться, но только склонен: в любом языке требуется некоторая избыточность для обнаружения ошибок. В любом случае вам следует присоединиться к команде разработчиков Felix, потому что это основная цель. Например, все объявления являются взаимно рекурсивными, и вы можете видеть как вперед, так и назад, поэтому вам не нужны прямые объявления (область видимости устанавливается по умолчанию, как метки C goto).

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

@Turing Complete: без повторения кода: это хорошая идея, но это неверно и невозможно. Существует огромное количество шаблонов использования, и мы хотим абстрагировать общие в библиотеке, но абстрагировать их все - безумие, потому что даже если бы мы могли, семантическая нагрузка запоминания всех имен слишком высока. Вам нужен хороший баланс. Не забывайте, что повторение - это то, что придает вещам структуру (шаблон подразумевает избыточность).

Common Lisp Object System (CLOS) - еще один пример чего-то, что поддерживает MI, избегая при этом проблем в стиле C ++: наследованию дается разумное значение по умолчанию , но при этом вы можете явно решать, как именно, скажем, вызывать поведение суперпользователя. ,

Да, CLOS - одна из самых совершенных объектных систем с момента зарождения современных вычислений, может быть, даже давно :)

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

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

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

На самом деле он несколько сломан в том смысле, что не вписывается в другие возможности C ++. Присваивание не работает должным образом с наследованием, не говоря уже о множественном наследовании (проверьте действительно плохие правила). Правильное создание ромбов настолько сложно, что комитет по стандартам испортил иерархию исключений, чтобы сделать ее простой и эффективной, вместо того, чтобы делать это правильно. На более старом компиляторе, который я использовал в то время, когда я тестировал это, и несколько миксинов MI и реализации базовых исключений стоили более мегабайта кода и занимали 10 минут, чтобы скомпилировать . только определения.

Бриллианты - хороший тому пример. В Eiffel алмаз разрешен явно. Например, представьте, что ученик и учитель наследуют от Person. У человека есть календарь, поэтому и ученик, и учитель унаследуют этот календарь. Если вы создаете ромб, создав TeachingStudent, который наследуется и от Teacher, и от Student, вы можете решить переименовать один из унаследованных календарей, чтобы оба календаря были доступны отдельно, или решить объединить их, чтобы он вел себя как Person. Множественное наследование можно реализовать красиво, но это требует тщательного проектирования, желательно с самого начала .

Казалось бы, W.Test() создание экземпляра W вызовет реализацию виртуального метода, Foo определенного в X . Однако предположим, что Y и Z на самом деле были в отдельно скомпилированном модуле, и хотя они были определены, как указано выше, при компиляции X и W, позже они были изменены и перекомпилированы:

Каким должен быть эффект от звонка W.Test() ? Если бы программа должна была быть статически связана перед распространением, на этапе статической ссылки можно было бы определить, что, хотя программа не имела двусмысленности до изменения Y и Z, изменения Y и Z сделали вещи неоднозначными, и компоновщик мог отказаться строить программу, пока такая неоднозначность не будет разрешена. С другой стороны, возможно, что человек, у которого есть как W, так и новые версии Y и Z, просто хочет запустить программу и не имеет исходного кода ни для одной из них. При W.Test() запуске уже не будет понятно, что W.Test() должен работать, но до тех пор, пока пользователь не попытается запустить W с новой версией Y и Z, никакая часть системы не сможет распознать наличие проблемы (если W не считался незаконным даже до изменений в Y и Z) ,

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

Еще один интересный пример ( не специфичный для C ++):

Здесь B используется только виртуальное наследование. So E содержит два B одинаковых s A . Таким образом, вы можете получить A* указатель , который указывает на E , но вы не можете бросить его к B* указателю , хотя объект является на самом деле B как таковой бросок неоднозначна, и эта неопределенность не может быть обнаружена во время компиляции (если компилятор не видит вся программа). Вот тестовый код:

Более того, реализация может быть очень сложной (зависит от языка; см. Ответ Бенджисмита).

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

Почему Java делает одиночное наследование и каковы недостатки множественного наследования

Хотя множественное наследование может сделать так, чтобы дочерний класс имел характеристики нескольких родительских классов одновременно, его недостатки также существенны, главным образом в двух аспектах:
(1) Если существует переменная экземпляра с одинаковым именем в нескольких родительских классах, унаследованных дочерним классом, дочерний класс будет иметь неоднозначность при обращении к переменной и не сможет определить, какую переменную родительского класса следует использовать. Например:
Класс ClassA:

Подкласс ClassC: (допускается множественное наследование между классами)

Каков будет результат запуска вышеуказанной программы? Выход 0 или 1?
(2) Если несколько родительских классов, унаследованных подклассом, имеют один и тот же метод, и подкласс не переопределил метод, то при вызове этого метода будет возникать неоднозначность, и невозможно будет определить, какой метод родительского класса следует вызывать. Например:
Класс ClassA:

Каков будет результат запуска вышеуказанной программы? Выход трех строк A, B и C равен 0 или 1?
Из-за вышеупомянутых фатальных недостатков в java запрещено наследовать родительский класс от нескольких классов;

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

Интеллектуальная рекомендация

Ежедневное упражнение 51-Java-функция 3-умножение двух чисел (8kyu)

тема Эта функция вызывается несколько раз и должна принимать два числа в качестве параметров и должна возвращать произведение двух параметров. Прецедент: Решение проблем My постскриптум черт возьми! Э.


Page-Break-До Pipement Свойства

Элемент пейджинговой объяснение, Page-Break-До: Auto. Все браузеры поддерживаются, и пейджинговой происходило перед элементом. Но внимание, если элемент Table, или это плавающий элемент (например: поп.

Обработка событий Vue

Обработка событий Модификатор события Очень часто требуется вызвать event.preventDefault () или event.stopPropagation () в обработчике событий. Хотя мы можем легко добиться этого в методе, лучший спос.


Apache часто не может получить доступ к списку каталогов и отображает 403 запрещенных

Apache часто не может получить доступ к списку каталогов и отображает 403 запрещенных При разработке и обучении каждый раз, когда я сбрасываю apache, я всегда сообщаю об ошибке: Запрещено У вас нет ра.


Очередь цикла в структуре данных (язык C)

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

Вам также может понравиться

Подключитесь к базе данных на сервере MySQL из локального _Navicat (от новичка до)

Здесь не будет описываться установка и активация графической операционной базы данных Navicat. При использовании программного обеспечения для подключения к MySQL на облачном сервере необходимо обратит.



Базовые синтаксические идентификаторы и зарезервированные слова Python

Содержание этой статьи взято из книги "Python Programming Case Class",Эта книга была добавлена ​​к правам VIP-членства. Пока вы являетесь VIP-членом, вы можете читать тысячи электронных книг.


Linux серии команд-ls команда подробно

(1) Функция: список файлов и подкаталогов, содержащихся в текущем рабочем каталоге (2) Синтаксис: параметры ls -a Показать все файлы и каталоги (ls по умолчанию - имя файла или имя каталога, начинающе.

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

Допустим, мы хотим написать программу, чтобы отслеживать группу преподавателей. Преподаватель – это человек. Однако преподаватель также является сотрудником (и, если он работает на себя, является работодателем). Множественное наследование можно использовать для создания класса Teacher (преподаватель), который наследует свойства как от Person (человек), так и от Employee (сотрудник). Чтобы использовать множественное наследование, просто укажите все базовые классы (как и в одиночном наследовании), разделив их запятыми.

Рисунок 1 Диаграмма наследования

Рисунок 1 – Диаграмма наследования

Проблемы с множественным наследованием

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

Во-первых, неоднозначность может возникнуть, если несколько базовых классов содержат функцию с одним и тем же именем. Например:

Когда c54G.getID() компилируется, компилятор проверяет, содержит ли WirelessAdapter функцию с именем getID() . Ее у него нет. Затем компилятор проверяет, есть ли в каком-либо из родительских классов функция с именем getID() . Видите здесь проблему? Проблема в том, что c54G на самом деле содержит ДВЕ функции getID() : одна унаследована от USBDevice , а другая – от NetworkDevice . Следовательно, этот вызов функции неоднозначен, и вы получите ошибку компиляции, если попытаетесь скомпилировать этот код.

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

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

Во-вторых, более серьезная проблема – проблема ромба (или англоязычный термин – diamond problem). Она появляется, когда класс множественно наследуется от двух классов, каждый из которых наследуется от одного базового класса. Это приводит к ромбовидной структуре наследования.

Например, рассмотрим следующий набор классов:

Рисунок 2 Ромбовидная структура наследования

Рисунок 2 – Ромбовидная структура наследования

Сканеры ( Scanner ) и принтеры ( Printer ) являются устройствами с питанием, поэтому они являются производными от PoweredDevice . Однако копировальный аппарат ( Copier ) включает в себя функции как сканеров, так и принтеров.

Множественное наследование – больше проблем, чем оно того стоит?

Многие авторы и опытные программисты считают, что множественного наследования в C++ следует избегать любой ценой из-за множества потенциальных проблем, которые оно приносит. Автор данного руководства не согласен с этим подходом, потому что бывают случаи и ситуации, когда множественное наследование – лучший способ решения задачи. Однако множественное наследование следует использовать крайне осторожно.

Интересно отметить, что вы уже использовали классы, написанные с использованием множественного наследования, даже не подозревая об этом: объекты библиотеки iostream std::cin и std::cout реализованы с использованием множественного наследования!

Правило

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

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

Множественное наследование

Класс А обобщенно наследует элементы всех трех основных классов.

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

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


В этих случаях необходимо использовать оператор разрешения контекста для уточнения элемента, к которому осуществляется доступ, именем базового класса.

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

  • конструкторы базовых классов в порядке их задания;
  • конструкторы членов, являющихся объектами класса;
  • конструктор порожденного класса.

Деструкторы вызываются в порядке обратном вызову конструкторов.

Виртуальные базовые классы

Иерархия наследования

Базовый класс может быть задан только один раз в списке порождения нового класса. Однако базовый класс может встретиться несколько раз в иерархии порождения.

Такая иерархия порождения несет двусмысленность при доступе к наследуемым членам класса X и может привести к ошибкам. В этом случае класс X будет дважды присутствовать в A . Хорошо это или плохо - зависит от решаемой задачи.

Если двойное вхождение объектов класса X в объект класса А не является допустимым, существует два выхода для разрешения такой ситуации:

    преобразование порождения из нескольких классов в порождение из одного класса и объявление дружественных классов.

Базовый класс определяется как виртуальный заданием ключевого слова virtual в списке порождения перед именем базового класса или указанием типа наследования


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

class A : public Y, public Z int key;
public :
A( int i = 0) : Y(i + 2), Z(i + 3)
key = Y::key + Z::key;
>
int getkey( void ) < return (key); >
>;
int main() A object(4);
cout "object.key = " object.getkey();
cin.get();
return 0;
>

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

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

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

короткий ответ: потому что языковые дизайнеры решили этого не делать.

для Java, вы можете прочитать в этой статье:

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

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

множественное наследование реализация это то, что не разрешено.

проблема в том, что компилятор / среда выполнения не может понять, что делать, если у вас есть класс Cowboy и Artist, оба с реализациями для метода draw (), а затем вы пытаетесь создать новый тип CowboyArtist. Что происходит, когда вы вызываете метод draw ()? Кто-то лежит мертвый на улице, или у вас есть прекрасная акварель?

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

причина: Java очень популярен и прост в коде, из-за его простоты.

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

  1. они избегали указатели
  2. они избежали множественного наследования.

проблема с множественным наследованием: Алмаз проблема.

пример:

  1. предположим, что класс A имеет метод fun (). класс B и класс C являются производными от класса A.
  2. и оба класса B и C, переопределяет метод fun().
  3. теперь предположим, что класс D наследует от обоих классов B и C. (просто предположение)
  4. создать объект класса D.
  5. D d = новый D ();
  6. и попробуйте получить доступ к d.fun (); = > будет ли он вызывать класс B fun () или класс C fun ()?

Это двусмысленность, существующая в алмазной проблеме.

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

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

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

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

кроме того, Java была в значительной степени реакцией на C++ и Smalltalk, наиболее известные языки OO. Существует множество других языков OO (Common Lisp был фактически первым стандартизированным), с различными системами OO, которые лучше обрабатывают MI.

Не говоря уже о том, что это вполне возможно сделать MI в Java, используя интерфейсы, композицию и делегирование. Он более явный, чем в C++, и поэтому более неуклюж в использовании, но даст вам то, что вы, скорее всего, поймете на первый взгляд.

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

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

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

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

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

Я беру утверждение ,что" множественное наследование не разрешено в Java " с щепоткой соли.

множественное наследование определяется, когда "тип" наследуется от нескольких "типов". И интерфейсы также классифицируются как типы, поскольку они имеют поведение. Таким образом, Java имеет множественное наследование. Просто так безопаснее.

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

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

проблема множественного наследования. У нас есть два класса B и C, наследуемых от A. предположим, что B и C переопределяют an унаследованный метод и они обеспечивают свою собственную реализацию. Теперь D наследует как B, так и C, выполняя множественное наследование. D должен наследовать этот переопределенный метод, jvm не может решить, какой переопределенный метод будет использоваться?

в c++ виртуальные функции используются для обработки, и мы должны делать это явно.

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

в старые времена (70-е), когда информатика была больше науки и меньше массового производства программисты имели время, чтобы думать о хорошем дизайне и хорошей реализации, и в результате продукты ( программы) имели высокое качество (например. Проектирование и реализация TCP/IP ). В настоящее время, когда все программируют, а менеджеры меняют спецификации до крайних сроков, тонкие проблемы, такие как описанные в ссылке Википедии от Steve Haigh post, трудно отслеживать; поэтому "множественное наследование" ограничено конструкцией компилятора. Если вам это нравится, вы все равно можете использовать C++ . и иметь всю свободу, которую вы хотите:)

фактически множественное наследование возникнет сложность, если унаследованные классы имеют одну и ту же функцию. т. е. компилятор будет путаницы, кто выбрал (проблема). Таким образом, в Java эта сложность удалена и дала интерфейс, чтобы получить функциональность, такую как множественное наследование. Мы можем использовать интерфейс

Java имеет концепцию, т. е. полиморфизм. В java существует 2 типа полиморфизма. Есть перегрузка метода и переопределение метода. Среди них переопределение метода происходит с отношениями супер-и подкласса. Если мы создаем объект подкласса и вызываем метод суперкласса, и если подкласс расширяет более одного класса, какой метод суперкласса следует вызвать?

или , при вызове конструктора суперкласса с помощью super() , который конструктор супер класса будет получить вызов?

эти решения невозможны текущими функциями API java. таким образом, множественное наследование не разрешено в java.

множественное наследование не разрешено в Java напрямую, но через интерфейсы это разрешено.

Множественное Наследование : вводит больше сложности и двусмысленности.

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

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

возьмем простой пример.

предположим, что есть 2 класса суперклассов A и B с одинаковыми именами методов но разные функции. Через следующий код с (extends) ключевым словом множественное наследование невозможно.

но через интерфейсы, с (реализует) ключевое слово множественное наследование возможно.

в C++ класс может наследовать (прямо или косвенно) более чем один класс, который называется множественное наследование.

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

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

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

в этом примере flag сведения член определяется как class A . Но!--4--> происходит из class B и class C , которые оба происходят от A , так что по сути две копии of flag доступны, потому что два примеры A в ’S класса. Что вы хотите установить? Компилятор будет жаловаться вот ссылка на flag на D и неоднозначные. Одним из исправлений является явное устранение двусмысленности ссылки:

другое исправление-объявить B и C as virtual base classes , что означает, что только одна копия может существуют в иерархии, устраняя любую двусмысленность.

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

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

может ли кто-нибудь сказать мне точно, почему это не разрешено?

вы можете найти ответ из этой документации ссылке

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

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

Что делать, если методы или конструкторы из разных суперклассов создают одно и то же поле?

какой метод или конструктор будет иметь приоритет?

хотя множественное наследование состояния теперь разрешено, все же вы можете реализовать

несколько наследование типа: способность класса реализовать более одного интерфейса.

множественное наследование реализации (через методы по умолчанию в интерфейсах): возможность наследовать определения методов из нескольких классов

см. этот связанный вопрос SE для получения дополнительной информации:

представьте себе такой пример: У меня есть класс Shape1

это CalcualteArea способ:

есть еще один класс Shape2 , что также имеет тот же метод

теперь у меня есть дочерний круг класса, он происходит от Shape1 и Shape2;

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

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

Читайте также: