Множественное наследование является особенностью некоторых объектно-ориентированным компьютерных языков программирования, в котором объект или класс может наследовать свойства и функцию из более чем один родительского объекта или родительского класса. Это отличается от одиночного наследования, когда объект или класс могут наследовать только от одного конкретного объекта или класса.
Множественное наследование было спорным вопросом в течение многих лет, и противники указывали на его повышенную сложность и неоднозначность в таких ситуациях, как «проблема ромба», когда может быть неоднозначно, от какого родительского класса наследуется конкретная функция, если более одного родительский класс реализует ту же функцию. Эту проблему можно решить разными способами, в том числе с помощью виртуального наследования. Альтернативные методы композиции объектов, не основанные на наследовании, такие как примеси и признаки, также были предложены для устранения неоднозначности.
В объектно-ориентированном программировании (ООП), наследование описывает взаимосвязь между двумя классами, в которых один класс ( ребенок класс) подклассы в родительском классе. Дочерний элемент наследует методы и атрибуты родителя, обеспечивая совместную функциональность. Например, можно создать переменный класс Mammal с такими функциями, как питание, размножение и т. Д.; затем определите дочерний класс Cat, который наследует эти функции без необходимости их явного программирования, добавляя при этом новые функции, такие как преследование мышей.
Множественное наследование позволяет программистам использовать более одного полностью ортогональным иерархии одновременно, например, позволяя Cat унаследовать Мультипликационный персонаж и домашних животных и млекопитающими и доступ к функциям из внутри всех этих классов.
Языки, поддерживающие множественное наследование, включают: C ++, Common Lisp (через Common Lisp Object System (CLOS)), EuLisp (через объектную систему EuLisp TELOS), Curl, Dylan, Eiffel, Logtalk, Object REXX, Scala (через использование классов миксинов). ), OCaml, Perl, POP-11, Python, R, Raku и Tcl (встроенные в 8.6 или через инкрементный Tcl ( Incr Tcl ) в более ранних версиях).
Среда выполнения IBM System Object Model (SOM) поддерживает множественное наследование, и любой язык программирования, ориентированный на SOM, может реализовывать новые классы SOM, унаследованные от нескольких баз.
Некоторые объектно-ориентированные языки, такие как Swift, Java, Fortran, начиная с его версии 2003 г., C # и Ruby реализуют единичное наследование, хотя протоколы или интерфейсы предоставляют некоторые функции истинного множественного наследования.
PHP использует классы признаков для наследования конкретных реализаций методов. Ruby использует модули для наследования нескольких методов.
« Проблема с бриллиантом » (иногда называемая «Смертельным алмазом смерти») - это неоднозначность, которая возникает, когда два класса 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 соединяет их вместе внизу, образуя ромбовидную форму.
В языках есть разные способы решения этих проблем повторного наследования.
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
тот же объект).D,B,C,A
, когда B написано перед C в определении класса. Выбирается метод с наиболее конкретными классами аргументов (Dgt; (B, C)gt; A); затем в том порядке, в котором родительские классы названы в определении подкласса (Bgt; C). Однако программист может переопределить это, указав конкретный порядок разрешения методов или установив правило для объединения методов. Это называется комбинацией методов, которой можно полностью управлять. MOP ( протокол метаобъектов ) также предоставляет средства для изменения наследования, динамической диспетчеризации, создания экземпляров класса и других внутренних механизмов, не влияя на стабильность системы.D
включает две структуры B
и C
обе имеют метод F()
, таким образом удовлетворяя интерфейс A
, компилятор будет жаловаться на «неоднозначный селектор», если D.F()
вызывается, или если экземпляр D
присваивается переменной типа A
. B
И C
методы можно вызывать явно с помощью D.B.F()
или D.C.F()
.A,B,C
интерфейсы, B,C
каждый может предоставить другую реализацию в качестве абстрактного метода в A
, вызывая проблему алмазов. Либо класс D
должен повторно реализовать метод (тело которого может просто перенаправить вызов одной из супер реализаций), либо двусмысленность будет отклонена как ошибка компиляции. До Java 8 Java не подвергалась риску проблемы Diamond, потому что она не поддерживала множественное наследование, а методы интерфейса по умолчанию были недоступны.(individual as Person).printInfo();
. superlt;ChosenParentInterfacegt;.someMethod()
B
и его предки будут проверяться до класса C
и его предков, поэтому метод in A
будет унаследован через B
. Это совместно с Ио и Пиколиспом. В Perl это поведение можно переопределить с помощью тех mro
или иных модулей, чтобы использовать линеаризацию C3 или другие алгоритмы.object
. Python создает список классов, используя алгоритм линеаризации C3 (или порядок разрешения методов (MRO)). Этот алгоритм применяет два ограничения: дочерние элементы предшествуют своим родителям, и если класс наследуется от нескольких классов, они сохраняются в порядке, указанном в кортеже базовых классов (однако в этом случае некоторые классы в верхнем графе наследования могут предшествовать классам ниже в график). Таким образом, разрешение порядка метод: D
, B
, C
, A
.D
, C
, A
, B
, A
], что снижает до [ D
, C
, B
, A
].Языки, допускающие только единичное наследование, где класс может быть производным только от одного базового класса, не имеют проблемы ромба. Причина этого в том, что такие языки имеют не более одной реализации любого метода на любом уровне в цепочке наследования, независимо от повторения или размещения методов. Обычно эти языки позволяют классам реализовывать несколько протоколов, называемых в Java интерфейсами. Эти протоколы определяют методы, но не предоставляют конкретных реализаций. Эта стратегия использовалась ActionScript, C #, D, Java, Nemerle, Object Pascal, Objective-C, Smalltalk, Swift и PHP. Все эти языки позволяют классам реализовывать несколько протоколов.
Более того, Ada, C #, Java, Object Pascal, Objective-C, Swift и PHP допускают множественное наследование интерфейсов (называемых протоколами в Objective-C и Swift). Интерфейсы похожи на абстрактные базовые классы, которые определяют сигнатуры методов без реализации какого-либо поведения. («Чистые» интерфейсы, такие как интерфейсы в Java до версии 7, не допускают никакой реализации или данных экземпляра в интерфейсе.) Тем не менее, даже если несколько интерфейсов объявляют одну и ту же сигнатуру метода, как только этот метод будет реализован (определен) в любом месте цепочки наследования он переопределяет любую реализацию этого метода в цепочке над ним (в его суперклассах). Следовательно, на любом заданном уровне в цепочке наследования может быть не более одной реализации любого метода. Таким образом, реализация метода одиночного наследования не проявляет проблемы ромба даже при множественном наследовании интерфейсов. С введением реализации по умолчанию для интерфейсов в Java 8 и C # 8 по-прежнему возможно создание проблемы Diamond, хотя это будет проявляться только как ошибка времени компиляции.
См. Также: Сравнение явной реализации интерфейса C Sharp и Java §