Множественное наследование
Эта статья нуждается в дополнительных цитатах для проверки . ( август 2012 г. ) |
Множественное наследование — это особенность некоторых объектно-ориентированных , языков программирования в которых объект или класс может наследовать функции более чем от одного родительского объекта или родительского класса . Оно отличается от одиночного наследования, когда объект или класс может наследовать только от одного конкретного объекта или класса.
Множественное наследование было спорным вопросом на протяжении многих лет. [1] [2] оппоненты указывают на его повышенную сложность и двусмысленность в таких ситуациях, как «проблема ромба», где может быть неясно, от какого родительского класса унаследована конкретная функция, если указанную функцию реализует более одного родительского класса. Эту проблему можно решить разными способами, в том числе с помощью виртуального наследования . [3] альтернативные методы композиции объектов, не основанные на наследовании, такие как примеси и черты Для устранения двусмысленности также были предложены .
Подробности
[ редактировать ]В объектно-ориентированном программировании (ООП) наследование описывает отношения между двумя классами, в которых один класс ( дочерний класс) подклассом родительского является класса. Дочерний элемент наследует методы и атрибуты родительского элемента, что позволяет использовать общие функции. Например, можно создать переменный класс Mammal с такими функциями, как поедание, размножение и т. д.; затем определите дочерний класс Cat , который наследует эти функции без необходимости их явного программирования, а также добавляет новые функции, такие как погоня за мышами .
Множественное наследование позволяет программистам использовать более одной полностью ортогональной иерархии одновременно, например, позволяя Cat наследовать от персонажа мультфильма , домашнего животного и млекопитающего и получать доступ к функциям всех этих классов.
Реализации
[ редактировать ]Языки, поддерживающие множественное наследование, включают: C++ , Common Lisp (через объектную систему Common Lisp (CLOS)), EuLisp (через объектную систему EuLisp TELOS), Curl , Dylan , Eiffel , Logtalk , Object REXX , Scala (с помощью миксинов классов- ). ), OCaml , Perl , POP-11 , Python , R , Raku и Tcl (встроено в версии 8.6 или через Incremental Tcl ( Incr Tcl ) в более ранних версиях [4] [5] ).
Среда выполнения IBM System Object Model (SOM) поддерживает множественное наследование, и любой язык программирования, предназначенный для SOM, может реализовать новые классы SOM, унаследованные от нескольких баз.
Некоторые объектно-ориентированные языки, такие как Swift , Java , Fortran начиная с версии 2003 года , C# и Ruby реализуют одиночное наследование , хотя протоколы или интерфейсы предоставляют некоторые функции истинного множественного наследования.
PHP использует классы признаков для наследования конкретных реализаций методов. Ruby использует модули для наследования нескольких методов.
Проблема алмазов
[ редактировать ]« Алмазная проблема » (иногда называемая «Смертельным алмазом смерти»). [6] ) — это неоднозначность, которая возникает, когда два класса B и C наследуют от A, а класс D наследует как от B, так и от C. Если в A есть метод, который B и C переопределили , а D не переопределяет его, то какая версия Какой метод наследует D: метод B или метод C?
Например, в контексте с графическим интерфейсом разработки программного обеспечения класс Button
может наследовать от обоих классов Rectangle
(для внешнего вида) и Clickable
(для функциональности/обработки ввода) и классы Rectangle
и Clickable
оба наследуют от Object
сорт. Теперь, если equals
метод вызывается для Button
объект и такого метода нет в Button
класс, но есть переопределенный equals
метод в Rectangle
или Clickable
(или оба), какой метод в конечном итоге следует вызвать?
Ее называют «проблемой ромба» из-за формы диаграммы наследования классов в этой ситуации. В этом случае класс A находится вверху, B и C по отдельности под ним, а D объединяет их внизу, образуя ромбовидную форму.
смягчение последствий
[ редактировать ]Языки по-разному решают проблемы повторного наследования.
- C# (начиная с C# 8.0) допускает реализацию метода интерфейса по умолчанию, в результате чего класс
A
, реализация интерфейсовIa
иIb
с аналогичными методами, имеющими реализации по умолчанию, иметь два «унаследованных» метода с одинаковой сигнатурой, что приводит к проблеме ромба. Это смягчается либо требованиемA
реализовать сам метод, тем самым устраняя двусмысленность или заставляя вызывающую сторону сначала привестиA
объект к соответствующему интерфейсу, чтобы использовать реализацию этого метода по умолчанию (например,((Ia) aInstance).Method();
). - C++ по умолчанию следует каждому пути наследования отдельно, поэтому
D
объект на самом деле будет содержать два отдельныхA
объекты и способы использованияA
члены должны иметь соответствующую квалификацию. Если наследство отA
кB
и наследство отA
кC
оба отмечены "virtual
" (например, "class B : virtual public A
"), C++ уделяет особое внимание созданию только одногоA
объект и способы его использованияA
Члены работают корректно. Если виртуальное и невиртуальное наследование смешаны, существует единое виртуальное наследование.A
и невиртуальныйA
для каждого невиртуального пути наследованияA
. C++ требует явного указания, из какого родительского класса вызывается используемая функция, т.е.Worker::Human.Age
. C++ не поддерживает явное повторное наследование, поскольку не будет возможности определить, какой суперкласс использовать (т. е. если класс появляется более одного раза в одном списке производных [класс Dog: public Animal, Animal]). C++ также позволяет создавать один экземпляр множественного класса с помощью механизма виртуального наследования (т. е.Worker::Human
иMusician::Human
будет ссылаться на один и тот же объект). - Common Lisp CLOS пытается обеспечить как разумное поведение по умолчанию, так и возможность его переопределить. По умолчанию, проще говоря, методы сортируются по
D,B,C,A
, когда B записан перед C в определении класса. Выбирается метод с наиболее конкретными классами аргументов (D>(B,C)>A) ; затем в том порядке, в котором родительские классы названы в определении подкласса (B>C). Однако программист может обойти это, задав определенный порядок разрешения методов или указав правило комбинирования методов. Это называется комбинацией методов, которую можно полностью контролировать. MOP ( протокол метаобъектов ) также предоставляет средства для изменения наследования, динамической диспетчеризации , создания экземпляров классов и других внутренних механизмов, не влияя на стабильность системы. - Curl только те классы, которые явно помечены как общие позволяет повторно наследовать . Общие классы должны определять вторичный конструктор для каждого обычного конструктора в классе. Обычный конструктор вызывается при первой инициализации состояния общего класса с помощью конструктора подкласса, а вторичный конструктор будет вызываться для всех остальных подклассов.
- В Eiffel функции предков выбираются явно с помощью директив select и rename. Это позволяет разделить функции базового класса между его потомками или предоставить каждому из них отдельную копию базового класса. Eiffel позволяет явно объединять или разделять функции, унаследованные от классов-предков. Eiffel автоматически объединит функции, если они имеют одинаковое имя и реализацию. У автора класса есть возможность переименовать унаследованные функции, чтобы разделить их. Множественное наследование — частое явление при разработке Эйфеля; Например, большинство эффективных классов в широко используемой библиотеке структур данных и алгоритмов EiffelBase имеют двух или более родителей. [7]
- Go предотвращает проблему ромба во время компиляции. Если структура
D
встраивает две структурыB
иC
оба из которых имеют методF()
, таким образом удовлетворяя интерфейсуA
, компилятор будет жаловаться на "неоднозначный селектор", еслиD.F()
вызывается, или если экземплярD
присваивается переменной типаA
.B
иC
методы могут быть вызваны явно с помощьюD.B.F()
илиD.C.F()
. - Java 8 представляет методы по умолчанию для интерфейсов. Если
A,B,C
являются интерфейсами,B,C
каждый из них может предоставить различную реализацию абстрактного методаA
, вызывая проблему с алмазами. Любой классD
должен переопределить метод (тело которого может просто перенаправить вызов одной из суперреализаций), иначе неоднозначность будет отклонена как ошибка компиляции. [8] До версии Java 8 Java не подвергалась риску проблемы Diamond, поскольку она не поддерживала множественное наследование, а методы интерфейса по умолчанию были недоступны. - JavaFX Script в версии 1.2 допускает множественное наследование посредством использования примесей . В случае конфликта компилятор запрещает прямое использование неоднозначной переменной или функции. Доступ к каждому унаследованному члену по-прежнему можно получить, приведя объект к интересующему миксину, например
(individual as Person).printInfo();
. - Kotlin допускает множественное наследование интерфейсов, однако в сценарии проблемы Diamond дочерний класс должен переопределить метод, вызывающий конфликт наследования, и указать, какую реализацию родительского класса следует использовать. например
super<ChosenParentInterface>.someMethod()
- Logtalk поддерживает множественное наследование как интерфейса, так и реализации, позволяя объявлять псевдонимы методов , которые обеспечивают как переименование, так и доступ к методам, которые были бы замаскированы механизмом разрешения конфликтов по умолчанию.
- В OCaml родительские классы указываются индивидуально в теле определения класса. Методы (и атрибуты) наследуются в том же порядке, причем каждый новый унаследованный метод переопределяет любые существующие методы. OCaml выбирает последнее соответствующее определение списка наследования классов, чтобы решить, какую реализацию метода использовать в случае неоднозначности. Чтобы переопределить поведение по умолчанию, нужно просто квалифицировать вызов метода с нужным определением класса.
- Perl использует список классов для наследования в виде упорядоченного списка. Компилятор использует первый найденный метод путем поиска в глубину списка суперклассов или использования линеаризации C3 иерархии классов. Различные расширения предоставляют альтернативные схемы композиции классов. Порядок наследования влияет на семантику класса. В приведенной выше двусмысленности класс
B
и его предки будут проверены перед классомC
и его предков, поэтому метод вA
будет унаследован черезB
. Это общее с Io и Picolisp . В Perl это поведение можно переопределить с помощьюmro
или другие модули для использования линеаризации C3 или других алгоритмов. [9] - Python имеет ту же структуру, что и Perl, но, в отличие от Perl, включает ее в синтаксис языка. Порядок наследования влияет на семантику класса. Python пришлось столкнуться с этим после появления классов нового стиля, каждый из которых имеет общего предка.
object
. Python создает список классов, используя алгоритм линеаризации C3 (или порядок разрешения методов (MRO)). Этот алгоритм накладывает два ограничения: дети предшествуют своим родителям, и если класс наследуется от нескольких классов, они сохраняются в порядке, указанном в кортеже базовых классов (однако в этом случае некоторые классы, расположенные выше в графе наследования, могут предшествовать классам, расположенным ниже в графе наследования). график [10] ). Таким образом, порядок разрешения метода следующий:D
,B
,C
,A
. [11] - Классы Ruby имеют только одного родителя, но могут также наследовать от нескольких модулей; Определения классов Ruby выполняются, и (пере)определение метода скрывает любое ранее существовавшее определение во время выполнения. В отсутствие метапрограммирования во время выполнения это имеет примерно ту же семантику, что и крайнее правое разрешение в глубину.
- Scala допускает множественное создание экземпляров типажей , что допускает множественное наследование путем добавления различия между иерархией классов и иерархией типажей. Класс может наследовать только один класс, но может смешивать любое количество признаков. Scala разрешает имена методов, используя поиск расширенных «признаков» справа в глубину, прежде чем исключать все, кроме последнего вхождения каждого модуля в результирующем списке. Итак, порядок разрешения: [
D
,C
,A
,B
,A
], что сводится к [D
,C
,B
,A
]. - Tcl допускает использование нескольких родительских классов; порядок спецификации в объявлении класса влияет на разрешение имен членов, использующих алгоритм линеаризации C3 . [12]
Языки, допускающие только одиночное наследование , где класс может быть производным только от одного базового класса, не имеют проблемы ромба. Причина этого в том, что такие языки имеют не более одной реализации любого метода на любом уровне цепочки наследования независимо от повторения или размещения методов. Обычно эти языки позволяют классам реализовывать несколько протоколов , называемых интерфейсами в Java. Эти протоколы определяют методы, но не предоставляют конкретных реализаций. Эта стратегия использовалась ActionScript , C# , D , Java , Nemerle , Object Pascal , Objective-C , Smalltalk , Swift и PHP . [13] Все эти языки позволяют классам реализовывать несколько протоколов.
Более того, Ada , C#, Java, Object Pascal, Objective-C, Swift и PHP допускают множественное наследование интерфейсов (называемых протоколами в Objective-C и Swift). Интерфейсы подобны абстрактным базовым классам, которые определяют сигнатуры методов без реализации какого-либо поведения. («Чистые» интерфейсы, такие как интерфейсы Java до версии 7, не допускают никакой реализации или данных экземпляра в интерфейсе.) Тем не менее, даже когда несколько интерфейсов объявляют одну и ту же сигнатуру метода, как только этот метод реализован (определен) в любом месте цепочки наследования он переопределяет любую реализацию этого метода в цепочке над ним (в его суперклассах). Следовательно, на любом уровне цепочки наследования может существовать не более одной реализации любого метода. Таким образом, реализация метода с одним наследованием не демонстрирует проблему ромба даже при множественном наследовании интерфейсов. С введением реализации по умолчанию для интерфейсов в Java 8 и C# 8 все еще возможно создать проблему ромба, хотя это будет проявляться только как ошибка времени компиляции.
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Каргилл, штат Техас (зима 1991 г.). «Противоречие: аргументы против множественного наследования в C++». Вычислительные системы . 4 (1): 69–82.
- ^ Уолдо, Джим (весна 1991 г.). «Противоречие: аргументы в пользу множественного наследования в C++». Вычислительные системы . 4 (2): 157–171.
- ^ Шерли, Натанаэль; Дюкасс, Стефан; Ньерстраз, Оскар; Блэк, Эндрю. «Черты характера: составные единицы поведения» (PDF) . Веб.cecs.pdx.edu . Проверено 21 октября 2016 г.
- ^ "инкр Tcl" . блог.tcl.tk. Проверено 14 апреля 2020 г.
- ^ «Введение в язык программирования Tcl» . www2.lib.uchicago.edu . Проверено 14 апреля 2020 г.
- ^ Мартин, Роберт К. (9 марта 1997 г.). «Java и C++: критическое сравнение» (PDF) . Objectmentor.com . Архивировано из оригинала (PDF) 24 октября 2005 г. Проверено 21 октября 2016 г.
- ^ «Стандарт ECMA-367» . Ecma-international.org . Проверено 21 октября 2016 г.
- ^ «Состояние лямбды» . Cr.openjdk.java.net . Проверено 21 октября 2016 г.
- ^ "перлобж" . perldoc.perl.org . Проверено 21 октября 2016 г.
- ^ Абстрактный. «Порядок разрешения методов Python 2.3» . Python.org . Проверено 21 октября 2016 г.
- ^ «Объединение типов и классов в Python 2.2» . Python.org . Проверено 21 октября 2016 г.
- ^ «Manpage класса» . Tcl.tk. 16 ноября 1999 г. Проверено 21 октября 2016 г.
- ^ «Объектные интерфейсы — Руководство» . PHP.net . 4 июля 2007 г. Проверено 21 октября 2016 г.
Дальнейшее чтение
[ редактировать ]- Страуструп, Бьярн (1999). Множественное наследование в C++ . Материалы конференции Европейской группы пользователей Unix весной 1987 г.
- Объектно-ориентированное создание программного обеспечения , второе издание, Бертран Мейер , Prentice Hall, 1997, ISBN 0-13-629155-4
- Эдди Труен; Воутер Йосен; Бо Норрегаард; Пьер Вербатен (2004). «Обобщение и решение проблемы дилеммы общего предка в объектных системах, основанных на делегировании» (PDF) . Материалы семинара по динамическим аспектам 2004 г. (103–119).
- Ира Р. Форман; Скотт Дэнфорт (1999). Использование метаклассов в работе . ISBN 0-201-43305-2 .