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

редактировать

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

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

СОДЕРЖАНИЕ

  • 1 Детали
  • 2 Реализации
  • 3 Проблема с алмазом
    • 3.1 Смягчение
  • 4 См. Также
  • 5 ссылки
  • 6 Дальнейшее чтение
  • 7 Внешние ссылки

Подробности

В объектно-ориентированном программировании (ООП), наследование описывает взаимосвязь между двумя классами, в которых один класс ( ребенок класс) подклассы в родительском классе. Дочерний элемент наследует методы и атрибуты родителя, обеспечивая совместную функциональность. Например, можно создать переменный класс 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?

Например, в контексте разработки программного обеспечения с графическим интерфейсом пользователя класс может наследовать как классы (для внешнего вида) и (для функциональности / обработки ввода), так и классы, и оба они наследуются от класса. Теперь, если метод вызывается для объекта и в классе нет такого метода, но есть переопределенный метод в или (или в обоих), какой метод следует в конечном итоге вызвать? ButtonRectangleClickableRectangleClickableObjectequalsButtonButtonequalsRectangleClickable

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

Смягчение

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

  • 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 в определении класса. Выбирается метод с наиболее конкретными классами аргументов (Dgt; (B, C)gt; A); затем в том порядке, в котором родительские классы названы в определении подкласса (Bgt; C). Однако программист может переопределить это, указав конкретный порядок разрешения методов или установив правило для объединения методов. Это называется комбинацией методов, которой можно полностью управлять. MOP ( протокол метаобъектов ) также предоставляет средства для изменения наследования, динамической диспетчеризации, создания экземпляров класса и других внутренних механизмов, не влияя на стабильность системы.
  • Curl позволяет повторно наследовать только классы, явно отмеченные как общие. Общие классы должны определять вторичный конструктор для каждого обычного конструктора в классе. Обычный конструктор вызывается при первой инициализации состояния общего класса с помощью конструктора подкласса, а вторичный конструктор вызывается для всех других подклассов.
  • В Eiffel свойства предков выбираются явно с помощью директив select и rename. Это позволяет разделять функции базового класса между его потомками или предоставлять каждому из них отдельную копию базового класса. Eiffel допускает явное объединение или разделение функций, унаследованных от классов-предков. Eiffel автоматически объединит объекты вместе, если они имеют одинаковое имя и реализацию. У автора класса есть возможность переименовать унаследованные функции, чтобы разделить их. Множественное наследование - частое явление в развитии Eiffel; например, большинство эффективных классов в широко используемой библиотеке структур данных и алгоритмов EiffelBase имеют двух или более родителей.
  • 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должен повторно реализовать метод (тело которого может просто перенаправить вызов одной из супер реализаций), либо двусмысленность будет отклонена как ошибка компиляции. До Java 8 Java не подвергалась риску проблемы Diamond, потому что она не поддерживала множественное наследование, а методы интерфейса по умолчанию были недоступны.
  • JavaFX Script версии 1.2 допускает множественное наследование с помощью миксинов. В случае конфликта компилятор запрещает прямое использование неоднозначной переменной или функции. К каждому унаследованному члену по-прежнему можно получить доступ, преобразовав объект в интересующий миксин, например (individual as Person).printInfo();.
  • Kotlin допускает множественное наследование интерфейсов, однако в сценарии проблемы Diamond дочерний класс должен переопределить метод, вызывающий конфликт наследования, и указать, какую реализацию родительского класса следует использовать. например superlt;ChosenParentInterfacegt;.someMethod()
  • Logtalk поддерживает множественное наследование интерфейсов и реализаций, позволяя объявлять псевдонимы методов, которые обеспечивают как переименование, так и доступ к методам, которые будут замаскированы механизмом разрешения конфликтов по умолчанию.
  • В OCaml родительские классы указываются индивидуально в теле определения класса. Методы (и атрибуты) наследуются в том же порядке, при этом каждый вновь унаследованный метод переопределяет любые существующие методы. OCaml выбирает последнее совпадающее определение списка наследования классов, чтобы решить, какую реализацию метода использовать при неоднозначности. Чтобы переопределить поведение по умолчанию, нужно просто квалифицировать вызов метода с помощью определения желаемого класса.
  • Perl использует список классов для наследования в виде упорядоченного списка. Компилятор использует первый метод, который он находит, путем поиска в глубину списка суперклассов или с использованием линеаризации C3 иерархии классов. Различные расширения предоставляют альтернативные схемы композиции классов. Порядок наследования влияет на семантику класса. В приведенной выше неоднозначности класс Bи его предки будут проверяться до класса Cи его предков, поэтому метод in Aбудет унаследован через B. Это совместно с Ио и Пиколиспом. В Perl это поведение можно переопределить с помощью тех mroили иных модулей, чтобы использовать линеаризацию C3 или другие алгоритмы.
  • Python имеет ту же структуру, что и Perl, но, в отличие от Perl, включает ее в синтаксис языка. Порядок наследования влияет на семантику класса. Python пришлось иметь дело с этим при введении новых классов, все из которых имеют общего предка, object. Python создает список классов, используя алгоритм линеаризации C3 (или порядок разрешения методов (MRO)). Этот алгоритм применяет два ограничения: дочерние элементы предшествуют своим родителям, и если класс наследуется от нескольких классов, они сохраняются в порядке, указанном в кортеже базовых классов (однако в этом случае некоторые классы в верхнем графе наследования могут предшествовать классам ниже в график). Таким образом, разрешение порядка метод: D, B, C, A.
  • Классы Ruby имеют ровно одного родителя, но также могут наследовать от нескольких модулей; Определения класса ruby ​​выполняются, и (повторное) определение метода скрывает любое ранее существовавшее определение во время выполнения. В отсутствие метапрограммирования времени выполнения это имеет примерно ту же семантику, что и разрешение крайнего правого значения глубины.
  • Scala позволяет создавать несколько экземпляров признаков, что позволяет использовать множественное наследование, добавляя различие между иерархией классов и иерархией признаков. Класс может наследовать только от одного класса, но может смешивать столько черт, сколько нужно. Scala разрешает имена методов, используя поиск в глубину по расширенным «признакам» справа налево, прежде чем исключить все, кроме последнего вхождения каждого модуля в результирующем списке. Таким образом, порядок разрешения: [ D, C, A, B, A], что снижает до [ D, C, B, A].
  • Tcl позволяет использовать несколько родительских классов; порядок спецификации в объявлении класса влияет на разрешение имен для членов, использующих алгоритм линеаризации C3.

Языки, допускающие только единичное наследование, где класс может быть производным только от одного базового класса, не имеют проблемы ромба. Причина этого в том, что такие языки имеют не более одной реализации любого метода на любом уровне в цепочке наследования, независимо от повторения или размещения методов. Обычно эти языки позволяют классам реализовывать несколько протоколов, называемых в 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 §

Смотрите также

использованная литература

дальнейшее чтение

внешние ссылки

Последняя правка сделана 2023-03-20 08:55:36
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте