Опубликован: 17.10.2005 | Уровень: специалист | Доступ: свободно
Лекция 15:

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

Переименование компонентов

Иногда при множественном наследовании возникает проблема конфликта имен (name clash). Ее решение - переименование компонентов (feature renaming) - не только снимает саму проблему, но и способствует лучшему пониманию природы классов.

Конфликт имен

Каждый класс обладает доступом ко всем компонентам своих родителей. Он может использовать их, не указывая тот класс, в котором они были описаны. После обработки inherit в классе class C inherit A ... метод f класса C становится известен как f. То же справедливо и для клиентов: при объявлении сущности x типа C вызов компонента записывается как x.f без каких-либо ссылок на A. Все метафоры "хромают", иначе можно было бы говорить, что наследование - форма усыновления: C усыновляет все компоненты A.

Усыновление не меняет присвоенных имен, и набор имен компонентов данного класса содержит наборы имен компонентов каждого его родителя.

А если родители класса разные компоненты назвали одним именем? Возникает противоречие, поскольку согласно установленному ранее правилу запрещена перегрузка имен: в классе имя компонента обозначает только один компонент. Это правило не должно нарушаться при наличии родителей класса. Рассмотрим пример:

class SANTA_BARBARA inherit
         LONDON
         NEW_YORK
feature
         ...
end-- class SANTA_BARBARA

Что предпринять, если LONDON и NEW_YORK имеют в своем составе компонент с именем, например, foo (нечто)?

Ни при каких обстоятельствах нельзя нарушить запрет перегрузки имен компонентов. Как следствие, класс SANTA_ BARBARA окажется некорректным, что обнаружится при трансляции.

Вспомним класс TREE, порожденный от классов CELL и LIST, каждый из которых имеет компонент с именем item. Кроме того, оба класса имеют метод, названный put. Выбор каждого имени не случаен, и мы не хотим менять их в исходных классах лишь потому, что кому-то пришла идея объединить эти классы в дерево.

Что делать? Исходный код классов LONDON и NEW_YORK может быть недоступен; или на его исправления может быть наложен запрет; а при отсутствии такого запрета, возможно, вам не захочется ничего менять, поскольку LONDON написан не вами, и выход новой версии класса заставит все начинать с нуля. Наконец, самое главное, принцип Открыт-Закрыт не разрешает исправлять модули при их повторном использовании.

Всегда ошибочно обвинять в грехах своих родителей. Проблема конфликта имен возникла в самом классе. В нем должно найтись и решение.

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

class SANTA_BARBARA inherit
         LONDON
                  rename foo as fog end
         NEW_YORK
feature
         ...
end

Как внутри SANTA_BARBARA, так и во всех клиентах этого класса компонент LONDON с именем foo будет именоваться fog, а одноименный компонент NEW_YORK - просто foo. Клиенты LONDON, как и прежде, будут знать этот компонент под именем foo.

Этого достаточно для устранения конфликта (если других совпадений нет, а класс LONDON и класс NEW_YORK не содержат компонента с именем fog ). В противном случае можно переименовать компонент класса NEW_YORK:

class SANTA_BARBARA inherit
         LONDON
                  rename foo as fog end
         NEW_YORK
                  rename foo as zoo end
feature
         ...
end

Предложение rename следует за указанием имени родителя и предшествует любым выражениям redefine, если таковые имеются. Можно переименовать и несколько компонентов, как в случае:

class TREE [G] inherit
         CELL [G]
                 rename item as node_item, put as put_right end

где устраняется конфликт между одноименными компонентами CELL и LIST. Компоненту CELL с именем item дается идентификатор node_item, аналогично и put переименовывается в put_right.

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

Убедимся, что нам понятен результат этого действия. Пусть класс SANTA_BARBARA имеет вид (оба унаследованных компонента foo в нем переименованы):

Устранение конфликта имен

Рис. 15.13. Устранение конфликта имен

(Обратите внимание на графическое обозначение операции смены имен.) Пусть также имеются сущности трех видов:

l: LONDON; n: NEW_YORK; s: SANTA_BARBARA

Вызовы l.foo и s.fog будут являться корректными. После полиморфного присваивания l := s все останется корректным, поскольку имена обозначают один и тот же компонент. Аналогично, корректны вызовы n.foo, s.zoo, которые после n := s также будут давать одинаковый результат.

В то же время, следующие вызовы некорректны:

  • l.zoo, l.fog, n.zoo, n.fog, так как ни LONDON, ни NEW_YORK не содержат компонентов с именем fog или zoo ;
  • s.foo, поскольку после смены имен класс SANTA_BARBARA уже не имеет компонента с именем foo.

При всей искусственности имен пример хорошо иллюстрирует природу конфликта имен. Хотите верьте, хотите нет, но приходилось слышать, что конфликт порождает "глубокую семантическую проблему". Это неправда. Конфликт имен - простая синтаксическая проблема. Если бы автор первого класса сменил имя компонента на fog, или автор второго - на zoo, конфликта бы не было, и в каждом случае - это всего лишь замена буквы. Конфликт имен - это обычная неудача, он не вскрывает никаких глубоких проблем, связанных с классами, и не свидетельствует об их неспособности работать совместно. Возвращаясь к метафоре брака, можно сказать, что конфликт имен - это не драма (обнаруженная несовместимость групп крови), а забавный факт (матери обоих супругов носят имя Татьяна, и это вызовет трудности для будущих внуков, которые можно преодолеть, договорившись, как называть обеих бабушек).

Смена имен и переопределение

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

  • Переопределение меняет компонент, но сохраняет его имя.
  • Переименование меняет имя, но сохраняет компонент.

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

Смена имен - это синтаксический механизм, позволяющий ссылаться на один и тот же компонент, фигурирующий в разных классах под разными именами.

Иногда то и другое можно совмещать:

class SANTA_BARBARA inherit
         LONDON
                  rename
                          foo as fog
                  redefine
                          fog
                  end
         ...

Если, как и раньше, l: LONDON ; s: SANTA_BARBARA, и выполнено присваивание l := s, то оба вызова l.foo, s.fog включают переопределенную версию компонента fog, объявление которого должно появиться в предложении feature класса.

Заметьте: redefine содержит уже новое имя компонента. Это нормально, поскольку под этим именем компонент известен классу. Именно поэтому rename должно находиться выше всех остальных предложений наследования (таких, как redefine и пока неизвестные читателю export, undefine, select ). После выполнения rename компонент теряет свой прежний идентификатор и становится известным под новым именем классу, его потомкам и его клиентам.

Александр Шалухо
Александр Шалухо
Как сбросить прогресс по курсу? Хочу начать заново
Анатолий Садков
Анатолий Садков
Вопросик
Александр Качанов
Александр Качанов
Япония, Токио
Янош Орос
Янош Орос
Украина, Киев