Книга: Основы объектно-ориентированного программирования

Пример повышенной сложности

Пример повышенной сложности

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

Проблема, близкая по духу нашему примеру, возникла из интересного обсуждения в основной книге по C++ [Stroustrup 1991].

Рассмотрим класс WINDOW с процедурой display и двумя наследниками: WINDOW_WITH_BORDER и WINDOW_WITH_MENU. Эти классы описывают абстрактные окна, первое из них имеет рамку, а второе поддерживает меню. Переопределяя display, каждый класс выводит на экран стандартное окно, а затем добавляет к нему рамку (в первом случае) и меню (во втором).

Опишем окно с рамкой и с поддержкой меню. В результате мы породим класс WINDOW_WITH_BORDER_AND_MENU.


Рис. 15.24.  Варианты окна

Переопределим метод display в новом классе; новая версия вначале вызывает исходную, затем строит рамку, а потом строит меню. Исходный класс WINDOW имеет вид:

class WINDOW feature
display is
-- Отобразить окно (общий алгоритм)
do
...
end
... Другие компоненты ...
end

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

class WINDOW_WITH_BORDER inherit
WINDOW
redefine display end
feature -- Output
display is
-- Рисует окно и его рамку.
do
Precursor
draw_border
end
feature {NONE} -- Implementation
draw_border is do ... end
...
end

Обратите внимание на процедуру draw_border, рисующую рамку окна. Она скрыта от клиентов класса WINDOW_WITH_BORDER (экспорт классу NONE), поскольку для них вызов draw_border не имеет смысла. Класс WINDOW_WITH_MENU аналогичен:

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.

Оглавление книги


Генерация: 1.291. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз