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

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

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

Шрифт:

-
+

Интервал:

-
+

Закладка:

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

class WINDOW_WITH_MENU inherit

WINDOW

redefine display end

feature -- Output

display is

-- Рисует окно и его меню.

do

Precursor

draw_menu

end

feature {NONE} -- Implementation

draw_menu is do ... end

...

end

Осталось описать общего наследника WINDOW_WITH_BORDER_AND_MENU этих двух классов, дублируемого потомка WINDOW. Предпримем первую попытку:

indexing

WARNING: "Первая попытка - версия не будет работать корректно!"

class WINDOW_WITH_BORDER_AND_MENU inherit

WINDOW_WITH_BORDER

redefine display end

WINDOW_WITH_MENU

redefine display end

feature

display is

-- Рисует окно,его рамку и меню.

do

Precursor {WINDOW_WITH_BORDER}

Precursor {WINDOW_WITH_MENU}

end

...

end

Заметьте: при каждом обращении к Precursor мы вынуждены называть имя предка. Каждый предок имеет собственный компонент display, переопределенный под тем же именем.

Впрочем, как замечает Страуструп, это решение некорректно: версии родителей дважды вызывают исходную версию display класса WINDOW, что приведет к появлению "мусора" на экране. Для исправления ситуации добавим еще один класс, получив тройку наследников класса WINDOW:

indexing

note: "Это корректная версия"

class WINDOW_WITH_BORDER_AND_MENU inherit

WINDOW_WITH_BORDER

redefine

display

export {NONE}

draw_border

end

WINDOW_WITH_MENU

redefine

display

export {NONE}

draw_menu

end

WINDOW

redefine display end

feature

display is

-- Рисует окно,его рамку и меню.

do

Precursor {WINDOW}

draw_border

draw_menu

end

...

end

Заметьте, что компоненты draw_border и draw_menu в новом классе являются скрытыми, поскольку мы не видим причин, по которым клиенты WINDOW_WITH_BORDER_AND_MENU могли бы их вызывать непосредственно.

Несмотря на активное применение дублируемого наследования, класс переопределяет все унаследованные им варианты display, что делает выражения select ненужными. В этом состоит преимущество спецификатора Precursor в сравнении с репликацией компонентов.

Неплохим тестом на понимание дублируемого наследования станет решение этой задачи без применения Precursor, путем репликации компонентов промежуточных классов. При этом, разумеется, вам понадобится select (см. упражнение 15.10).

В полученном варианте класса присутствует лишь совместное использование, но не репликация компонентов. Расширим пример Страуструпа: пусть WINDOW имеет запрос id (возможно, целого типа), направленный на идентификацию окон. Если идентифицировать любое окно только одним "номером", то id будет использоваться совместно, и нам не придется ничего менять. Если же мы хотим проследить историю окна, то экземпляр WINDOW_WITH_BORDER_AND_MENU будет иметь три id - независимых "номера". Новый текст класса комбинирует совместное использование и репликацию id (изменения в тексте класса помечены стрелками):

indexing

note: "Усложненная версия с независимыми id."

class WINDOW_WITH_BORDER_AND_MENU inherit

WINDOW_WITH_BORDER

rename

id as border_id

redefine

display

export {NONE}

draw_border

end

WINDOW_WITH_MENU

rename

id as menu_id

redefine

display

export {NONE}

draw_menu

end

WINDOW

rename

id as window_id

redefine

display

select

window_id

end

feature

.... Остальное, как ранее...

end

Обратите внимание на необходимость выбора (select) одного из вариантов id.

Дублируемое наследование и универсальность

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

class A [G] feature

f: G;...

end

class B inherit

A [INTEGER]

A [REAL]

end

В классе B по правилу дублируемого наследования компонент f должен использоваться совместно. Но из-за универсализации возникает неоднозначность, - какой результат должен возвращать компонент - real или integer? Та же проблема возникнет, если f имеет параметр типа G.

Подобная неоднозначность недопустима. Отсюда правило:

Универсальность в правиле дублируемого наследования

Тип компонента, совместно используемого в правиле дублируемого наследования, а также тип любого из его аргументов не может быть родовым параметром класса, от которого произошло дублируемое наследование компонента.

Для устранения неоднозначности можно выполнить переименование в точке наследования.

Правила об именах

(В этом разделе мы только формализуем сказанное выше, поэтому при первом чтении книги его можно пропустить.)

Мы уже видели, что в случае возможной неоднозначности конфликты имен пресекаются, хотя некоторые ситуации бывают вполне корректны. Чтобы в представлении множественного и дублируемого наследования не оставить никакой неоднозначности, полезно обобщить ограничения на конфликт имен в едином правиле: Заканчивая этот раздел, сведем изложенный ранее материал в единое правило:

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

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

Конфликт имен делает класс некорректным за исключением следующих случаев:

1 Оба компонента унаследованы от общего предка, и ни один из них не получен повторным объявлением версии предка.

2 Оба компонента имеют совместимые сигнатуры, и, по крайней мере, один из них наследуется в отложенной форме.

3 Оба компонента имеют совместимые сигнатуры и переопределяются в новом классе.

Ситуация (1) описывает совместное использование при дублируемом наследовании.

Для случая (2) "наследование в отложенной форме" возможно по двум причинам: либо отложенная форма задана родительским классом, либо компонент был эффективным, но порожденный класс отменил его реализацию (undefine).

Ситуации (2) и (3) рассматриваются отдельно, однако, их можно представить как один вариант - вариант соединения (join). Переходя к n компонентам (n >= 2), можно сказать, что ситуации (2) и (3) возникают, когда от разных родителей класс принимает n одноименных компонентов с совместимыми сигнатурами. Конфликт имен не делает класс некорректным, если эти компоненты могут быть соединены, иными словами:

[x]. все n компонентов отложены, так что некому вызвать конфликт определений;

[x]. существует единственный эффективный компонент. Его реализация станет реализацией остальных компонентов;

[x]. два или несколько компонентов эффективны. Класс должен их переопределить. Новая реализация будет использоваться как для переопределяемых компонентов, так и для любых отложенных компонентов, участвующих в конфликте.

И, наконец, точное правило употребления конструкции Precursor. Если в переопределении используется Precursor, то неоднозначность может возникнуть из-за того, что неясно, версию какого родителя следует вызывать. Чтобы решить эту проблему, следует использовать вызов вида Precursor {PARENT} (...), где PARENT - имя желаемого родителя. В остальных случаях указывать имя родителя не обязательно.

Обсуждение

Давайте проанализируем следствия некоторых решений, принятых в этой лекции.

Переименование

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

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