чем плохо множественное наследование
Русские Блоги
Почему Java делает одиночное наследование и каковы недостатки множественного наследования
Хотя множественное наследование может сделать так, чтобы дочерний класс имел характеристики нескольких родительских классов одновременно, его недостатки также существенны, главным образом в двух аспектах:
(1) Если существует переменная экземпляра с одинаковым именем в нескольких родительских классах, унаследованных дочерним классом, дочерний класс будет иметь неоднозначность при обращении к переменной и не сможет определить, какую переменную родительского класса следует использовать. Например:
Класс ClassA:
Подкласс ClassC: (допускается множественное наследование между классами)
Каков будет результат запуска вышеуказанной программы? Выход 0 или 1?
(2) Если несколько родительских классов, унаследованных подклассом, имеют один и тот же метод, и подкласс не переопределил метод, то при вызове этого метода будет возникать неоднозначность, и невозможно будет определить, какой метод родительского класса следует вызывать. Например:
Класс ClassA:
Каков будет результат запуска вышеуказанной программы? Выход трех строк A, B и C равен 0 или 1?
Из-за вышеупомянутых фатальных недостатков в java запрещено наследовать родительский класс от нескольких классов;
В интерфейсе не может быть переменных экземпляра, только статические константы, никаких конкретных методов (включая тела методов) и только абстрактные методы, поэтому недостаток множественного наследования также устраняется.
В случае, когда класс реализует несколько интерфейсов, поскольку интерфейс имеет только абстрактные методы, конкретные методы могут быть реализованы только классом, который реализует интерфейс. Когда он вызывается, всегда вызываются только методы реализующего класса (нет двусмысленности), поэтому нет множественного наследования. Вторым недостатком является то, что, поскольку интерфейс имеет только статические константы, а статические переменные определяются во время компиляции, даже если существует определенный конфликт, это вызовет ошибку во время компиляции, тогда как ссылки на статические переменные обычно используют имя класса напрямую. Или имена интерфейсов, чтобы избежать двусмысленности, поэтому нет первого недостатка множественного наследования.
Эти недостатки не существуют для случая, когда интерфейс наследует несколько родительских интерфейсов.
Чем чревато использование множественного наследования?
Есть дерево класов
Можно ли както реорганизовать дерево классов так чтобы класс C обладая свойствами и der1 и der2 не использовал множественное наследование? для меня критично чтобы класс А ничего не наследовал от der2 a B от der1.
юзай композицию, вместо наследования
Ишшо френды бывают.
Можно ли както реорганизовать дерево классов
Можно, если знать, зачем тебе нужно множественное наследование вообще.
ну а так вообще можно попортить семантику производного класса и получить кота с кустом вместо лапы и шлангом вместо хвоста (но мы же не о быдлокодерах говорим?)
при использовании базовых классов с непересекающимися контрактиами
думай о будущем, контракты они начнут пересекаться завтра, да
goto тоже 🙂 Мало ли кому чего не нужно. Далеко не всегда код с которым имеешь дело пишется тобой и так как ты хочешь 🙂
Что плохого во множественном наследовании? Оно естественно для ООП. Читайте Б.Мейера(да и Буча тоже).
да я вообще стараюсь его не использовать, кроме как в совсем простецких случаях типа:
class Entity: public IRotatable, public IMovable <>;
это абсолютно не значит, что при внесении своих изменений надо писать говно, как авторы кода.
там есть множественное наследование поведения на интерфейсах. или уже выпилили?
Ага-ага. Враппер на враппере врапером враппер врапит. Есть любители этого дела. Тоже знаком.
IШтобл*? подозрительный коденг конвеншын 🙂
IDniwe только для классов с pure virtual методами 🙂
смотря как ты его используешь
и лучше не скрещевать его при наследовании от фреймворков, а то я как-то уже сделал:
Вопрос в том, зачем класс С наследует der1 и der2. Можно попробовать один из них сделать членом класса, все публичные методы этого класса продублировать в C и пробросить их в член класса. Не помню, как такой трюк называется, но судя по всему, именно его и следует здесь применить.
Это криворучки из M$ постарались.
В MFC множественное наследование использовать нельзя. Для кого документацию вообще пишут?
что подразумеваешь под композицией?
Вопрос в том, зачем класс С наследует der1 и der2. Можно попробовать один из них сделать членом класса, все публичные методы этого класса продублировать в C и пробросить их в член класса. Не помню, как такой трюк называется, но судя по всему, именно его и следует здесь применить.
класс base определяет API. множество его потомков делятся на три группы: группа A имеет общую реализацию половины методов базового класса, группа B имеет общую реализацию другой половины методов базового класса, а третья группа имеет реализацию состоящую из общих частей группы А и группы B. Поэтому общую часть реализации методов для группы A я сделал в классе der1, для группы B в классе der2 а как быть с группой C я никак не могу определится.
В общем случае часть базы может изменятся классами der1 или der2, как реакция на работу самого класса. При этом множество всех непротиворечивых состояний для der1 вполне может не совпадать с множеством непротиворечивых состояний для der2.
В случае множественного наследования ты получаешь две базы, каждая из которых находится в некотором состоянии. При этом нужно постоянно следить за тем, чью именно базу ты используешь, чтобы не получить epic fail с чужой базой, которая не подходит для работы.
В подобных случаях лучше все же вынести интерфейсы ider1 и ider2, сами классы унаследовать через приватные члены и «пробросить» все функции интерфейсов в классы.
Ипать вы плюсоводы извращенцы.
PS: если вам требуется множественное наследование, есть мнение что у Вас что-то не так с архитектурой.
Главное не кури не наследуй классы с реализациями методов.
Если вам требуется множественное наследование, есть мнение что у Вас что-то не так с архитектурой.
100 раз эту мантру слышал от неосиляторов множественного наследования.
Какие-нибудь обоснованные подтверждения этой фразы вообще существуют?
Чревато ООП головного мозга.
классическое наследование в большинстве случаев не нужны, а вот миксины нужны, во всяком случае я так считаю.
почему? и какие альтернативы?
Поэтому общую часть реализации методов для группы A я сделал в классе der1, для группы B в классе der2 а как быть с группой C я никак не могу определится.
А зачем вам собственно класс base нужен? Ну определите часть api в der1, часть в der2 и объедините их в C. Сейчас у вас получается полная фигня. Наследование означает, что везде где используется base, можно использовать der1, а у вас он реализует только часть api, что концептуально не верно.
Урок №161. Множественное наследование
До сих пор мы рассматривали только одиночные наследования, когда дочерний класс имеет только одного родителя. Однако C++ предоставляет возможность множественного наследования.
Множественное наследование
Множественное наследование позволяет одному дочернему классу иметь несколько родителей. Предположим, что мы хотим написать программу для отслеживания работы учителей. Учитель — это Human. Тем не менее, он также является Сотрудником (Employee).
Множественное наследование может быть использовано для создания класса Teacher, который будет наследовать свойства как Human, так и Employee. Для использования множественного наследования нужно просто указать через запятую тип наследования и второй родительский класс:
Проблемы с множественным наследованием
Хотя множественное наследование кажется простым расширением одиночного наследования, оно может привести к множеству проблем, которые могут заметно увеличить сложность программ и сделать кошмаром дальнейшую поддержку кода. Рассмотрим некоторые из подобных ситуаций.
Во-первых, может возникнуть неоднозначность, когда несколько родительских классов имеют метод с одним и тем же именем, например:
При компиляции c54G.getID() компилятор смотрит, есть ли у WirelessAdapter метод getID(). Этого метода у него нет, поэтому компилятор двигается по цепочке наследования вверх и смотрит, есть ли этот метод в каком-либо из родительских классов. И здесь возникает проблема — getID() есть как у USBDevice, так и у NetworkDevice. Следовательно, вызов этого метода приведет к неоднозначности и мы получим ошибку, так как компилятор не будет знать какую версию getID() ему вызывать.
Тем не менее, есть способ обойти эту проблему. Мы можем явно указать, какую версию getID() следует вызывать:
Хотя это решение довольно простое, но всё может стать намного сложнее, если наш класс будет иметь от 4 родительских классов, которые, в свою очередь, будут иметь свои родительские классы. Возможность возникновения конфликтов имен увеличивается экспоненциально с каждым добавленным родительским классом, и в каждом из таких случаев нужно будет явно указывать версии методов, которые следует вызывать, дабы избежать возможности возникновения конфликтов имен.
Во-вторых, более серьезной проблемой является «алмаз смерти» (или «алмаз обреченности»). Это ситуация, когда один класс имеет 2 родительских класса, каждый из которых, в свою очередь, наследует свойства одного и того же родительского класса. Иллюстративно мы получаем форму алмаза.
Например, рассмотрим следующие классы:
Сканеры и принтеры — это устройства, которые получают питание от розетки, поэтому они наследуют свойства PoweredDevice. Однако ксерокс (Copier) включает в себя функции как сканеров, так и принтеров.
В этом контексте возникает много проблем, включая неоднозначность при вызове методов и копирование данных PoweredDevice в класс Copier дважды. Хотя большинство из этих проблем можно решить с помощью явного указания, поддержка и обслуживание такого кода может привести к непредсказуемым временным затратам. Мы поговорим детально о способах решения проблемы «алмаза смерти» на соответствующем уроке.
Стоит ли использовать множественное наследование?
Большинство задач, решаемых с помощью множественного наследования, можно решить и с использованием одиночного наследования. Многие объектно-ориентированные языки программирования (например, Smalltalk, PHP) даже не поддерживают множественное наследование. Многие, относительно современные языки, такие как Java и C#, ограничивают классы одиночным наследованием обычных классов, но допускают множественное наследование интерфейсных классов. Суть идеи, запрещающей множественное наследование в этих языках, заключается в том, что это излишняя сложность, которая порождает больше проблем, чем удобств.
Многие опытные программисты считают, что множественное наследование в языке C++ следует избегать любой ценой из-за потенциальных проблем, которые могут возникнуть. Однако все же остается вероятность, когда множественное наследование будет лучшим решением, нежели придумывание двухуровневых «костылей».
Стоит отметить, что вы сами уже использовали классы, написанные с использованием множественного наследования, даже не подозревая об этом: такие объекты, как std::cin и std::cout библиотеки iostream, реализованы с использованием множественного наследования!
Правило: Используйте множественное наследование только в крайних случаях, когда задачу нельзя решить одиночным наследованием, либо другим альтернативным способом (без изобретения «велосипеда»).
Множественное наследование в Java. Композиция в сравнении с Наследованием
Множественное наследование в Java
Ромбовидная проблема
Множественное наследование Интерфейсов
Композиция против Наследования
Предположим, что у нас есть суперкласс и класс, расширяющий его:
Обратите внимание, что метод test() уже существует в подклассе, но тип возвращаемого значения отличается. Теперь класс ClassD не будет компилироваться и если вы будете использовать какой-либо IDE, то она вам предложит изменить тип возвращаемого значения в суперклассе или подклассе.
Теперь представьте себе ситуацию, когда мы имеем многоуровневую иерархию наследования классов и не имеем доступа к суперклассу. У нас не будет никакого выбора, кроме как изменить нашу сигнатуру метода подкласса или его имя, чтобы удалить ошибку компиляции. Также мы должны будем изменить метод подкласса во всех местах, где он вызывается. Таким образом, наследование делает наш код хрупким.
Вышеупомянутая проблема никогда не произойдет с композицией и это делает ее более привлекательной по отношению к наследованию.
Другая проблема с наследованием состоит в том, что мы предоставляем все методы суперкласса клиенту и если наш суперкласс должным образом не разработан и есть пробелы в системе безопасности, то даже при том, что мы наилучшим образом выполняем реализацию нашего класса, на нас влияет плохая реализация суперкласса. Композиция помогает нам в обеспечении управляемого доступа к методам суперкласса, тогда как наследование не обеспечивает управления методами суперкласса. Это также одно из основных преимуществ композиции от наследования.
Результат программы представленной выше:
Эта гибкость при вызове метода не доступна при наследовании, что добавляет еще один плюс в пользу выбора композиции.
Поблочное тестирование легче делать при композиции, потому что мы знаем, что все методы мы используем от суперкласса и можем копировать их для теста. Тогда как в наследовании мы зависим в большей степени от суперкласса и не знаем всех методов суперкласса, которые будут использоваться. Таким образом, мы должны тестировать все методы суперкласса, что является дополнительной работой из-за наследования.
В идеале мы должны использовать наследование только когда отношение подкласса к суперклассу определяется как «является». Во всех остальных случаях рекомендуется использовать композицию.
Почему наследование всегда было бессмысленным
Есть три типа наследования.
Часто для наследования в ООП приводят контрпример отношений между квадратом и прямоугольником. Геометрически квадрат — это специализация прямоугольника: все квадраты — прямоугольники, но не все прямоугольники — квадраты. Все s в классе «Квадрат» являются прямоугольниками s, у которых длина равна ширине. Но в иерархии типов это отношение обратное: вы можете использовать прямоугольник везде, где используется квадрат (указав прямоугольник с одинаковой шириной и высотой), но нельзя использовать квадрат везде, где используется прямоугольник (например, вы не можете изменить длину и ширину).
Обратите внимание, что здесь налицо несовместимость между направлением наследования геометрических свойств и свойств абстрактного типа данных у квадратов и прямоугольников. Эти два измерения совершенно не связаны друг с другом ни в какой программной реализации. Мы ещё ничего не сказали о наследовании реализации, так что даже не рассматривали написание программы.
Smalltalk и многие более поздние языки используют простое наследование для наследования реализации, потому что множественное наследование несовместимо с ним из-за проблемы ромба (типажи предоставляют надёжный способ объявить несовместимость, оставляя решение проблемы в качестве упражнения для читателя). С другой стороны, простое наследование несовместимо с онтологическим наследованием, поскольку квадрат является одновременно прямоугольником и равносторонним многоугольником.
Синяя книга по Smalltalk описывает наследование исключительно с точки зрения наследования реализации:
«Подкласс определяет, что все его экземпляры будут, за исключением явно указанных отличий, такими же, как экземпляры другого класса, называемого его суперклассом».
Обратите внимание на отсутствующую деталь: не упоминается, что экземпляр подкласса должен быть в состоянии заменить экземпляр суперкласса везде в программе; не упоминается, что экземпляр подкласса должен удовлетворять всем концептуальным тестам для экземпляра своего суперкласса.
Наследование никогда не было проблемой: проблема в попытке использовать одно дерево для трёх разных концепций.
«Предпочитать структуру вместо наследования» — это по сути отказаться от наследования реализации. Мы не можем понять, как заставить его работать, так что давайте вовсе от него откажемся: сделаем совместное использование через делегирование, а не подклассы.
Eiffel и отдельные упорядоченные подходы к использованию языков вроде Java укрепляют отношение «наследование есть создание подтипов», ослабляя отношение «наследование есть повторное использование» (если один и тот же метод появляется дважды в несвязанных частях дерева, вам придётся с этим жить для сохранения свойства, что каждый подкласс является подтипом своего родителя). Это нормально, если вы не пытаетесь также смоделировать и проблемную область с помощью дерева наследования. Обычно литература по ООП рекомендует сделать это, когда речь идёт о проблемно-ориентированном проектировании.
Типажи укрепляют отношение «наследование есть специализация», ослабляя отношение «наследование есть повторное использование» (если обе суперкатегории производят одно и то же свойство экземпляра, то ни одно из них не наследуется и вы должны прописать его самостоятельно). Это нормально, если вы не пытаетесь также рассматривать подклассы как ковариантные подтипы своих суперклассов, но обычно литература по ООП рекомендует сделать это, упоминая принцип подстановки Барбары Лисков и то, что тип в сигнатуре метода означает этот тип или любой подкласс.
Я считаю, что в литературе должно быть написано следующее: «вот три типа наследования, сосредоточьтесь на одном из них». Я также считаю, что языки должны поддерживать это (очевидно, Smalltalk, Ruby и их друзья поддерживают это за счёт отсутствия каких-либо ограничений типов).
Вот теперь наследование опять стало простым.