Наследование (программирование)
- 1 year ago
- 0
- 0
Ромбовидное наследование
(
англ.
diamond inheritance
) — ситуация в
объектно-ориентированных
языках программирования
с поддержкой
множественного наследования
, когда два
класса
B
и
C
наследуют
от
A
, а класс
D
наследует от обоих классов
B
и
C
. При этой схеме наследования может возникнуть неоднозначность: если
объект
класса
D
вызывает метод, определенный в классе
A
(и этот метод не был
переопределен
в классе
D
), а классы
B
и
C
по-своему переопределили этот метод, то от какого класса его наследовать:
B
или
C
?
Например, в области
разработки
графических интерфейсов
класс
Button
(«Кнопка») может одновременно наследовать от класса
Rectangle
(«Прямоугольник», для внешнего вида) и от класса
Clickable
(«Доступен для кликанья мышкой», для реализации функциональности/обработки ввода), а
Rectangle
и
Clickable
наследуют от класса
Object
(«Объект»). Если вызвать метод
equals
(«Равно») для объекта
Button
, и в классе
Button
не окажется такого метода, но в классе
Object
будет присутствовать метод
equals
по-своему переопределенный как в классе
Rectangle
, так и в
Clickable
, то какой из методов должен быть вызван?
Проблема ромба (
англ.
diamond problem
) получила своё название благодаря очертаниям диаграммы наследования классов в этой ситуации. В данной статье, класс
A
обозначается в виде вершины, классы
B
и
C
по отдельности указываются ниже, а
D
соединяется с обоими в самом низу, образуя
ромб
.
Различные языки программирования решают проблему ромбовидного наследования следующими способами:
D
будет на самом деле содержать два разных подобъекта
A
, и при использовании членов
A
потребуется указать путь наследования (
B::A
или
C::A
). Чтобы сгенерировать ромбовидную структуру наследования, необходимо воспользоваться
виртуальным
наследованием класса
A
на нескольких путях наследования: если оба наследования от
A
к
B
и от
A
к
C
помечаются спецификатором
virtual
(например,
class B : virtual public A
), C++ специальным образом проследит за созданием только одного подобъекта
A
, и использование членов
A
будет работать корректно. Если
виртуальное
и невиртуальное наследования смешиваются, то получается один виртуальный подобъект
A
и по одному невиртуальному подобъекту
A
для каждого пути невиртуального наследования к
A
. При виртуальном вызове метода виртуального базового класса используется так называемое правило доминирования: компилятор запрещает виртуальный вызов метода, который был перегружен на нескольких путях наследования.
select
и
rename
, и методы предка, которые используются в потомках, указываются явно. Это позволяет совместно использовать методы родительского класса в потомках или предоставлять им отдельную копию родительского класса.
B
и его предки будут проверены перед классом
C
и его предками, так что метод в
A
будет унаследован от
B
; список разрешения — [
D
,
B
,
A
,
C
]. При этом в Perl данное поведение может быть изменено при помощи
mro
или других модулей для применения
C3-линеаризации
(как в Python) или других алгоритмов.
object
; начиная с этой версии было решено создавать список разрешения при помощи
C3-линеаризации
. В случае ромба это означает
поиск в глубину
, начиная слева (
D
,
B
,
A
,
C
,
A
), а затем удаление из списка всех, кроме последнего включения каждого класса, который в списке повторяется. Следовательно, итоговый порядок разрешения выглядит так: [
D
,
B
,
C
,
A
].
D
,
C
,
A
,
B
,
A
], а после удаления повторений — [
D
,
C
,
B
,
A
].
(individual as Person).printInfo();
.
Языки, допускающие лишь простое наследование (как например, Ада , Objective-C , PHP , C# , Delphi / Free Pascal и Java ), предусматривают множественное наследование интерфейсов (в Objective-C называемых протоколами). Интерфейсы по сути являются абстрактными базовыми классами, все методы которых также абстрактны, и где отсутствуют поля. Таким образом, проблема не возникает, так как всегда будет только одна реализация определенного метода или свойства, не допуская возникновения неопределенности.
Проблема ромба не ограничивается лишь наследованием. Она также возникает в таких языках, как
Си
и
C++
, когда
заголовочные файлы
A, B, C и D, а также отдельные
предкомпилированные заголовки
, созданные из B и C, подключаются (при помощи инструкции
#include
) один к другому по ромбовидной схеме, указанной вверху. Если эти два предкомпилированных заголовка объединяются, объявления в A дублируются, и директива
защиты подключения
#ifndef
становится неэффективной. Также проблема обнаруживается при объединении стеков
подпрограммного обеспечения
; например, если A — это база данных, а B и C —
, то D может запросить как B, так и C подтвердить (
COMMIT
) выполнение транзакции, приводя к дублирующим вызовам подтверждений A.