Категории
Самые читаемые
Лучшие книги » Компьютеры и Интернет » Прочая околокомпьтерная литература » Основы объектно-ориентированного программирования - Бертран Мейер

Основы объектно-ориентированного программирования - Бертран Мейер

Читать онлайн Основы объектно-ориентированного программирования - Бертран Мейер

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 152 153 154 155 156 157 158 159 160 ... 188
Перейти на страницу:

Переименовав один из компонентов, мы с легкостью сделаем D корректным:

class D inherit

-- Этот вариант класса теперь полностью корректен.

B

rename portends_trouble as does_not_portend_trouble_any_more end

C

end

Конфликт переопределений

Пока в ходе наследования мы меняли лишь имена. А что, если промежуточный предок, такой, как B или C (см. последний рисунок), переопределит дублируемо наследуемый компонент? При динамическом связывании это может привести к неоднозначности в D.

Проблему решают два простых механизма: отмена определения (undefinition) и выделение (selection). Как обычно, вы сами примете участие в их разработке и убедитесь в том, что при четкой постановке задачи нужная конструкция языка становится совершенно очевидной.

Пусть дублируемо наследуемый компонент переопределяется в одной из ветвей:

Рис. 15.21.  Переопределение - причина потенциальной неоднозначности

Класс B переопределяет f. Поэтому в D этот компонент представлен в двух вариантах: результат переопределения в B и исходный вариант из A, полученный через класс C. (Можно предполагать, что и C переопределяет f, но это не внесет в наше рассуждение ничего нового.) Такое положение дел отличается от предыдущих случаев, в которых мы имели лишь один вариант компонента, возможно, наследуемый под разными именами.

Что произойдет в результате? Ответ зависит от того, под одним или разными именами класс D наследует варианты компонентов. Подразумевает ли дублируемое наследование репликацию или совместное использование? Рассмотрим эти случаи по порядку.

Конфликт при совместном использовании: отмена определения и соединение компонентов

Предположим вначале, что две версии наследуются под одним и тем же именем. Это случай совместного использования. Одному имени должен в точности соответствовать один компонент. Возможны три ситуации.

1 Если одна версия отложена, а другая - эффективна, то сложностей не возникает, будет использован эффективный вариант компонента. Заметим, что этот случай явно предусмотрен правилом одного имени: речь в нем идет лишь о конфликте имен двух эффективных версий.

2 Каждая версия эффективна, однако обе они переопределяются в D в предложении redefine. Проблемы снова не возникает, поскольку обе версии сливаются в одну, переопределяемую в тексте класса.

3 Обе версии эффективны, но обе не переопределяются, тогда действительно возникает конфликт имен. Класс D будет отвергнут, как нарушающий правило одного имени.

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

Есть и другая, более изощренная возможность решения конфликта (3). Она состоит в том, чтобы позволить одному из вариантов "взять верх" над другим. Дальнейшее очевидно - свести эту ситуацию к (1), сделав один из двух вариантов отложенным.

Правила переопределения дают возможность переопределить компонент f как отложенный, хотя для этого и потребуется ввести промежуточный класс, скажем C', - наследника C, единственная роль которого - в переопределении отложенного f . Затем класс D должен быть порожден не от C, а от C'. Сложно и некрасиво. Вместо этого нам нужен простой языковой механизм: undefine. В секции наследования класса он приводит к появлению нового предложения:

class D inherit

B

C

undefine f end

feature

...

end

Синтаксически предложение undefine следует за rename (всякая отмена определения должна действовать на окончательный вариант имени компонента), но до redefine (прежде, чем что-то переопределять, мы должны позаботиться об отмене ненужных определений).

Признаком того, что предлагаемый языковой механизм желателен, почти всегда является его направленность на решение нескольких проблем (соответственно, плохой механизм создает больше проблем, чем решает). Механизм отмены определений отвечает этому требованию: он позволяет соединять компоненты в условиях множественного (не обязательно - дублируемого) наследования. Пусть мы хотим свести воедино две абстракции:

Рис. 15.22.  Два родителя и слияние компонентов

Мы хотим, чтобы D трактовал f и g как один компонент. Очевидно, это возможно лишь при условии совместимости семантики и сигнатур обоих компонентов (числа и типов аргументов и результата, если он есть). Допустим, что имена компонентов различны, и мы хотели бы сохранить имя f. Добиться желаемого можно, объединив переименование с отменой определения:

class D inherit

B

C

rename

g as f

undefine

f

end

feature

...

end

B получил полное превосходство над C, передавая классу D как сам компонент, так и его имя. Возможны и другие сочетания: компонент можно получить от одного из родителей, имя - от другого; можно переименовать оба компонента, присвоив им новое имя в D.

Еще один, более "симметричный" вариант соединения компонентов, заключается в замене обоих унаследованных вариантов на новый компонент. Достаточно указать оба компонента в предложении redefine, убедившись предварительно, что оба компонента имеют одно и то же финальное имя (добавив, если надо, выражение rename). В результате конфликта имен не возникнет (случай (2)), а объединение двух вариантов даст новый компонент.

Конфликты при репликации: выделение

Рассмотрим теперь случай конфликтов переопределений, связанных с репликацией. Пусть при дублируемом наследовании происходит переопределение и переименование эффективного компонента, так что имеем два эффективных компонента, наделенных собственными именами.

Рис. 15.23.  Необходимость выделения

Представленный на рисунке класс B меняет имя f на bf и переопределяет сам компонент. При этом мы опять полагаем, что C никак не меняет f, иное предположение нисколько не повлияет на ход нашего рассуждения. Более того, результат остался бы прежним, если бы B переопределял компонент f без его переименования, которое мы могли отложить до описания D. Допустим также, что речь не идет о соединении компонентов (которое происходит при переопределении обоих или отмене определения одного).

Поскольку компоненты наследуются под разными именами, то происходит их репликация. Класс D получает пару независимых компонентов, которые, в отличие от предыдущих случаев репликации, не являются копиями одного и того же компонента.

В отличие от случая совместного использования не возникает конфликта имен. Однако возникают другие конфликты, относящиеся к динамическому связыванию. Пусть полиморфная сущность a1 типа A (общий предок) на этапе выполнения связывается с экземпляром типа D (общим потомком). Что тогда означает вызов a1.f?

Правило динамического связывания гласит: вызываемый вариант f выбирается с учетом типа цели - объекта D. Но теперь это впервые нельзя истолковать однозначно: D содержит два равноценных варианта, известных под именами f и bf, соответствующих оригиналу f класса A.

Как и при конфликте имен, нельзя позволять компилятору делать выбор, пользуясь собственными правилами, - это противоречило бы принципам ясности и надежности. Управление ситуацией должно оставаться за автором разработки.

Для устранения неоднозначности необходим простой языковой механизм - предложение select. Вот версия класса, в которой предпочтение при динамическом связывании сущности f типа A отдается версии класса C:

class D inherit

B

C

1 ... 152 153 154 155 156 157 158 159 160 ... 188
Перейти на страницу:
На этой странице вы можете бесплатно скачать Основы объектно-ориентированного программирования - Бертран Мейер торрент бесплатно.
Комментарии