Ромбовидное наследование: различия между версиями

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
[непроверенная версия][непроверенная версия]
Содержимое удалено Содержимое добавлено
м викификация, орфография, пунктуация
Нет описания правки
Строка 10: Строка 10:


Различные языки программирования решают данную проблему следующими способами:
Различные языки программирования решают данную проблему следующими способами:
* [[C++]] по умолчанию обрабатывает каждый путь наследования отдельно, в результате чего объект <code>D</code> будет на самом деле содержать два разных объекта <code>A</code>, и использование членов <code>A</code> будет соответствующим образом обработано. Если наследование от <code>A</code> к <code>B</code> и наследование от <code>A</code> к <code>C</code> оба помечаются как "<code>virtual</code>" (виртуальные) (например, "<code>class B : virtual public A</code>"), C++ специальным образом проследит за созданием только одного объекта <code>A</code>, и использование членов <code>A</code> будет работать корректно. Если [[виртуальное наследование|виртуальное]] и невиртуальное наследования смешиваются, то получается один виртуальный <code>A</code> и один невиртуальный <code>A</code> для каждого пути невиртуального наследования к <code>A</code>.
* [[C++]] по умолчанию обрабатывает каждый путь наследования отдельно, в результате чего объект <code>D</code> будет на самом деле содержать два разных объекта <code>A</code>, и использование членов <code>A</code> будет соответствующим образом обработано. Если наследование от <code>A</code> к <code>B</code> и наследование от <code>A</code> к <code>C</code> оба помечаются как «<code>virtual</code>» (виртуальные) (например, «<code>class B : virtual public A</code>»), C++ специальным образом проследит за созданием только одного объекта <code>A</code>, и использование членов <code>A</code> будет работать корректно. Если [[виртуальное наследование|виртуальное]] и невиртуальное наследования смешиваются, то получается один виртуальный <code>A</code> и один невиртуальный <code>A</code> для каждого пути невиртуального наследования к <code>A</code>.
* [[Common Lisp]] пытается реализовать оба разумных поведения по умолчаниюи возможность переопределять его. По умолчанию выбирается метод с наиболее специфичными аргументами для соответствующего класса; затем по порядку, в котором родительские классы указаны при определении подкласса. Однако, программист вполне может это переопределить путем указания специального порядка разрешения методов или указания правила для объединения методов.
* [[Common Lisp]] пытается реализовать оба разумных поведения по умолчанию и возможность переопределять его. По умолчанию выбирается метод с наиболее специфичными аргументами для соответствующего класса; затем по порядку, в котором родительские классы указаны при определении подкласса. Однако, программист вполне может это переопределить путем указания специального порядка разрешения методов или указания правила для объединения методов.
* [[Eiffel]] обрабатывет подобную ситуацию путем выбора и переименования директив, в которых методы предка используются в потомках явно указанным образом. Это позволяет методам базового класса сообща использоваться в его потомках или даже предоставлять им отдельную копию базового класса.
* [[Eiffel]] обрабатывает подобную ситуацию путем выбора и переименования директив, в которых методы предка используются в потомках явно указанным образом. Это позволяет методам базового класса сообща использоваться в его потомках или даже предоставлять им отдельную копию базового класса.
* [[Perl]] и [[Io]] обрабатывают такую ситуацию путём указания порядка наследования классов в виде упорядоченного списка. В случае неопределенности, описанной выше, класс <code>B</code> и его предки будут проверены перед классом <code>C</code> и его предками, так что метод в <code>A</code> будет унаследован от <code>B</code>. В Perl это поведение может быть переопределено при помощи <code>mro</code> или других модулей для применения [[C3-линеаризация|C3-линеаризации]], либо других алгоритмов.
* [[Perl]] и [[Io]] обрабатывают такую ситуацию путём указания порядка наследования классов в виде упорядоченного списка. В случае неопределенности, описанной выше, класс <code>B</code> и его предки будут проверены перед классом <code>C</code> и его предками, так что метод в <code>A</code> будет унаследован от <code>B</code>. В Perl это поведение может быть переопределено при помощи <code>mro</code> или других модулей для применения [[C3-линеаризация|C3-линеаризации]], либо других алгоритмов.
* [[Python]] должен разбирать ситуацию перед указанием классов нового стиля, все из которых имеют общего предка <code>object</code>. Python создает список классов, которые будут искаться, начиная слева (<code>D</code>, <code>B</code>, <code>A</code>, <code>C</code>, <code>A</code>), а затем убирает все, кроме последнего подключения любого из повторяющихся классов. Таким образом, порядок разрешения выглядит следующим образом: <code>D</code>, <code>B</code>, <code>C</code>, <code>A</code>.
* [[Python]] должен разбирать ситуацию перед указанием классов нового стиля, все из которых имеют общего предка <code>object</code>. Python создает список классов, которые будут искаться, начиная слева (<code>D</code>, <code>B</code>, <code>A</code>, <code>C</code>, <code>A</code>), а затем убирает все, кроме последнего подключения любого из повторяющихся классов. Таким образом, порядок разрешения выглядит следующим образом: <code>D</code>, <code>B</code>, <code>C</code>, <code>A</code>.
* [[Scala (язык программирования)|Scala]] использует метод разрешения имен при помощи поиска по шаблону, начиная справа, в ходе чего удаляется все, кроме последнего вхождения каждого модуля в итоговый список. Так, итоговый порядок будет выглядеть следующим образом: [<code>D</code>, <code>C</code>, <code>A</code>, <code>B</code>, <code>A</code>], который сокращается до [<code>D</code>, <code>C</code>, <code>B</code>, <code>A</code>].
* [[Scala (язык программирования)|Scala]] использует метод разрешения имен при помощи поиска по шаблону, начиная справа, в ходе чего удаляется все, кроме последнего вхождения каждого модуля в итоговый список. Так, итоговый порядок будет выглядеть следующим образом: [<code>D</code>, <code>C</code>, <code>A</code>, <code>B</code>, <code>A</code>], который сокращается до [<code>D</code>, <code>C</code>, <code>B</code>, <code>A</code>].
* [[JavaFX Script]], начиная с версии 1.2, предусматривает множественное наследование за счет применения [[Примесь (программирование)|примесей]]. В случае конфликта, компилятор запретит прямое использование неопределенных переменных или функции. К каждому наследуемому члену по-прежнему будет возможен доступ за счет приведения объекта к нужной примеси, например, <code>(individual as Person).printInfo();</code>.
* [[JavaFX Script]], начиная с версии 1.2, предусматривает множественное наследование за счет применения [[Примесь (программирование)|примесей]]. В случае конфликта, компилятор запретит прямое использование неопределенных переменных или функции. К каждому наследуемому члену по-прежнему будет возможен доступ за счет приведения объекта к нужной примеси, например, <code>(individual as Person).printInfo();</code>.


==Прочие примеры==
== Прочие примеры ==
Языки, допускающие лишь простое наследование (как например, [[Ада (язык программирования)|Ада]], [[Objective-C]], [[PHP]], [[C Sharp|C#]], [[Delphi]]/[[Free Pascal]] и [[Java]]), предусматривают множественное наследование интерфейсов (называемые протоколами в Objective-C). Интерфейсы по сути являются абстрактными базовыми классами, все методы которых также абстрактны, и нет данных-членов. Таким образом, проблема не возникает, так как всегда будет только одна реализация определенного метода или свойства, не допуская возникновения неопределенности.
Языки, допускающие лишь простое наследование (как например, [[Ада (язык программирования)|Ада]], [[Objective-C]], [[PHP]], [[C Sharp|C#]], [[Delphi]]/[[Free Pascal]] и [[Java]]), предусматривают множественное наследование интерфейсов (называемые протоколами в Objective-C). Интерфейсы по сути являются абстрактными базовыми классами, все методы которых также абстрактны, и нет данных-членов. Таким образом, проблема не возникает, так как всегда будет только одна реализация определенного метода или свойства, не допуская возникновения неопределенности.


Проблема алмаза не ограничивается лишь наследованием. Она также возникает в таких языках, как [[Си (язык программирования)|Си]] и [[C++]] когда [[Заголовочный файл|заголовочные файлы]] A, B, C и D подключаются (при помощи инструкции <code>#include</code>) один к другому по "алмазообразной" схеме, указанной вверху, а также отдельные [[Предкомпированный заголовок|предкомпилированные заголовки]], созданные из B и C. Если эти два предкомпилированных заголовка объединяются, объявления в A дублируются, и директива [[Include guard|защиты подключения]] <code>#ifndef</code> становится неэффективной. Также проблема обнаруживается при объединении стеков [[Подпрограммное обеспечение|подпрограммного обеспечения]]; например, если A - это база данных, а B и C - [[Кэширование баз данных|кэши]], то D может запросить как B, так и C подтвердить ([[COMMIT]]) выполнение транзакции, приводя к дублирующим вызовам подтверждений A.
Проблема алмаза не ограничивается лишь наследованием. Она также возникает в таких языках, как [[Си (язык программирования)|Си]] и [[C++]], когда [[Заголовочный файл|заголовочные файлы]] A, B, C и D, а также отдельные [[Предкомпированный заголовок|предкомпилированные заголовки]], созданные из B и C, подключаются (при помощи инструкции <code>#include</code>) один к другому по «алмазообразной» схеме, указанной вверху. Если эти два предкомпилированных заголовка объединяются, объявления в A дублируются, и директива [[Include guard|защиты подключения]] <code>#ifndef</code> становится неэффективной. Также проблема обнаруживается при объединении стеков [[Подпрограммное обеспечение|подпрограммного обеспечения]]; например, если A — это база данных, а B и C — [[Кэширование баз данных|кэши]], то D может запросить как B, так и C подтвердить ([[COMMIT]]) выполнение транзакции, приводя к дублирующим вызовам подтверждений A.


==Литература==
== Литература ==
* {{ Cite journal
* {{ Cite journal
| issue = 103-119
| issue = 103-119

Версия от 03:44, 3 апреля 2010

Диаграмма наследования классов в виде алмаза.

В объектно-ориентированных языках программирования с поддержкой множественного наследования и структуры накопления знаний (knowledge organization) Проблема алмаза (diamond problem) — неопределенность, возникающая, когда два класса B и C наследуют от A, а класс D наследует от обоих классов B и C. Если метод класса D вызывает метод, определенный в классе A (и этот метод не был переопределен), а классы B и C по-своему переопределили этот метод, то тогда возникает вопрос — от какого класса его наследовать: B или C?

Например, в области разработки графических интерфейсов класс Button (Кнопка) может наследовать от обоих классов Rectangle (Прямоугольник) (для внешнего вида) и Clickable (Доступен для кликанья мышкой) (для реализации функционала/обработки ввода), а классы Rectangle и Clickable наследуют от класса Object (Объект). Теперь если вызвать метод equals (Равно) для объекта Button, и в классе Button не окажется такого метода, но вместо этого будет присутствовать переопределенный по-своему метод equals в обоих классах Rectangle и Clickable, то какой из методов должен быть вызван?

Данная проблема получила свое название «Проблема алмаза» (diamond problem) благодаря очертаниям диаграммы наследования классов в этой ситуации, напоминающим очертания граненого алмаза. В данной статье, класс A обозначается в виде вершины, классы B и C по отдельности указываются ниже, а D соединяется с обоими в самом низу, образуя контур алмаза.

Решения

Различные языки программирования решают данную проблему следующими способами:

  • C++ по умолчанию обрабатывает каждый путь наследования отдельно, в результате чего объект D будет на самом деле содержать два разных объекта A, и использование членов A будет соответствующим образом обработано. Если наследование от A к B и наследование от A к C оба помечаются как «virtual» (виртуальные) (например, «class B : virtual public A»), C++ специальным образом проследит за созданием только одного объекта A, и использование членов A будет работать корректно. Если виртуальное и невиртуальное наследования смешиваются, то получается один виртуальный A и один невиртуальный A для каждого пути невиртуального наследования к A.
  • Common Lisp пытается реализовать оба разумных поведения по умолчанию и возможность переопределять его. По умолчанию выбирается метод с наиболее специфичными аргументами для соответствующего класса; затем по порядку, в котором родительские классы указаны при определении подкласса. Однако, программист вполне может это переопределить путем указания специального порядка разрешения методов или указания правила для объединения методов.
  • Eiffel обрабатывает подобную ситуацию путем выбора и переименования директив, в которых методы предка используются в потомках явно указанным образом. Это позволяет методам базового класса сообща использоваться в его потомках или даже предоставлять им отдельную копию базового класса.
  • Perl и Io обрабатывают такую ситуацию путём указания порядка наследования классов в виде упорядоченного списка. В случае неопределенности, описанной выше, класс B и его предки будут проверены перед классом C и его предками, так что метод в A будет унаследован от B. В Perl это поведение может быть переопределено при помощи mro или других модулей для применения C3-линеаризации, либо других алгоритмов.
  • Python должен разбирать ситуацию перед указанием классов нового стиля, все из которых имеют общего предка object. Python создает список классов, которые будут искаться, начиная слева (D, B, A, C, A), а затем убирает все, кроме последнего подключения любого из повторяющихся классов. Таким образом, порядок разрешения выглядит следующим образом: D, B, C, A.
  • Scala использует метод разрешения имен при помощи поиска по шаблону, начиная справа, в ходе чего удаляется все, кроме последнего вхождения каждого модуля в итоговый список. Так, итоговый порядок будет выглядеть следующим образом: [D, C, A, B, A], который сокращается до [D, C, B, A].
  • JavaFX Script, начиная с версии 1.2, предусматривает множественное наследование за счет применения примесей. В случае конфликта, компилятор запретит прямое использование неопределенных переменных или функции. К каждому наследуемому члену по-прежнему будет возможен доступ за счет приведения объекта к нужной примеси, например, (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.

Литература

  • Eddy Truyen (2004). "A Generalization and Solution to the Common Ancestor Dilemma Problem in Delegation-Based Object Systems". Proceedings of the 2004 Dynamic Aspects Workshop (103–119). {{cite journal}}: Неизвестный параметр |coauthors= игнорируется (|author= предлагается) (справка)