Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
perimeter: REAL is
-- Сумма длин ребер
local
this, previous: POINT
do
from
vertices.start; this := vertices.item
check not vertices.after end -- Следствие условия at_least_three
until
vertices.is_last
loop
previous := this
vertices.forth
this := vertices.item
Result := Result + this.distance (previous)
end
Result := Result + this.distance (vertices.first)
end
В этом цикле просто последовательно складываются расстояния между соседними вершинами. Функция distance была определена в классе POINT. Значение Result, возвращаемое этой функцией, при инициализации получает значение 0. Из класса LINKED_LIST используются следующие компоненты: first дает первый элемент списка, start сдвигает курсор, на этот первый элемент, forth передвигает его на следующий, item выдает значение элемента под курсором, is_last определяет, является ли текущий элемент последним, after узнает, что курсор оказался за последним элементом. Как указано в команде check инвариант at_least_three обеспечивает правильное начало и завершение цикла. Он стартует в состоянии not after, в котором элемент vertices.item определен. Допустимо применение forth один или более раз, что, в конце концов, приведет в состояние, удовлетворяющее условию выхода из цикла is_last.
Прямоугольники
Предположим теперь, что нам требуется новый класс, представляющий прямоугольники. Можно было бы начать его проектировать заново. Но прямоугольники это специальный вид многоугольников и у них много общих компонент: их также можно сдвигать, поворачивать и выводить на экран. С другой стороны, у них есть ряд специфических компонентов (например, диагонали), специальные свойства (число вершин равно четырем, а углы являются прямыми) и возможны специальные варианты некоторых операций (вычисление периметра можно устроить проще, чем в приведенном выше алгоритме).
Преимущества такой смеси общих и специфических компонентов можно использовать, определив класс RECTANGLE как наследника (heir) класса POLYGON. При этом все компоненты класса POLYGON, называемого родителем (parent) класса RECTANGLE, по умолчанию будут применимы и к классу-наследнику. Для этого достаточно включить в RECTANGLE предложение наследования (inheritance clause):
class RECTANGLE inherit
POLYGON
feature
... Компоненты, специфичные для прямоугольников ...
end
В предложении feature класса-наследника компоненты родителя не повторяются: они автоматически доступны благодаря предложению о наследовании. В нем будут указаны лишь компоненты, специфичные для наследника. Это могут быть новые компоненты, такие как diagonal, а также переопределяемые наследуемые компоненты.
Вторая возможность полезна для такого компонента, который уже имелся у родителя, но у наследника должен быть описан в другом виде. Рассмотрим периметр perimeter. Для прямоугольников его можно вычислить более эффективно: не нужно вычислять четыре длины сторон, достаточно удвоить сумму длин двух сторон. Наследник, переопределяющий некоторый компонент родителя, должен объявить об этом в предложении наследования, включив предложение redefine:
class RECTANGLE inherit
POLYGON
redefine perimeter end
feature
...
end
Это позволяет включить в предложение feature класса RECTANGLE новую версию компонента perimeter, которая заменит его версию из класса POLYGON. Если не включить объявление redefine, то новое объявление компонента perimeter среди других компонентов класса RECTANGLE приведет к ошибке, поскольку у RECTANGLE уже есть компонент perimeter, унаследованный от POLYGON, т.е. у некоторого компонента окажется два определения.
Класс RECTANGLE выглядит следующим образом:
indexing
description: "Прямоугольники, - специальный случай многоугольников"
class RECTANGLE inherit
POLYGON
redefine perimeter end
creation
make
feature -- Инициализация
make (center: POINT; s1, s2, angle: REAL) is
-- Установить центр прямоугольника в center, длины сторон
-- s1 и s2 и ориентацию angle.
do ... end
feature -- Access
side1, side2: REAL
-- Длины двух сторон
diagonal: REAL
-- Длина диагонали
perimeter: REAL is
-- Сумма длин сторон
-- (Переопределение версии из POLYGON)
do
Result := 2 S (side1 + side2)
end
invariant
four_sides: count = 4
first_side: (vertices.i_th (1)).distance (vertices.i_th (2)) = side1
second_side: (vertices.i_th (2)).distance (vertices.i_th (3)) = side2
third_side: (vertices.i_th (3)).distance (vertices.i_th (4)) = side1
fourth_side: (vertices.i_th (4)).distance (vertices.i_th (1)) = side2
end
Для списка i_th(i) дает элемент в позиции i ( i-й элемент, следовательно это имя запроса).Так как RECTANGLE является наследником класса POLYGON, то все компоненты родительского класса применимы и к новому классу: vertices, rotate, translate, perimeter (в переопределенном виде) и все остальные. Их не нужно повторять в определении нового класса.
Этот процесс транзитивен: всякий класс, будучи наследником RECTANGLE, например, SQUARE, также обладает всеми компонентами класса POLYGON.
Основные соглашения и терминология
Кроме терминов "наследник" и "родитель" будут полезны следующие термины:
Терминология наследования
Потомок класса C - это любой класс, который наследует C явно или неявно, включая и сам класс C. (Формально, это либо C, либо, по рекурсии, потомок некоторого наследника C).
Собственный потомок класса C - это потомок, отличный от самого C.
Предок C - это такой класс A, для которого C является потомком. Собственный предок C - это такой класс A, для которого C является собственным потомком.
В литературе также встречаются термины "подкласс" и "суперкласс", но мы не будем их использовать из-за неоднозначности.
Имеется также терминология для компонентов класса: компонент либо является наследуемым (перешедшим от некоторого собственного предка), либо непосредственным (введенным в данном классе).
При графическом представлении структур ОО-ПО, в котором классы изображаются эллипсами, связи по отношению наследования показываются в виде одинарных стрелок. Тем самым они отличаются от связей по отношению "быть клиентом", которые представляются двойными стрелками.
Рис. 14.1. Связь по наследованию
Переопределяемый компонент отмечается ++ (это соглашение принято в Business Object Notation (B.O.N.)).
Стрелка указывает вверх от наследника к родителю. Это соглашение легко запомнить - оно представляет отношение "наследовать от". В литературе встречается и обратное направление таких стрелок. Хотя обычно выбор графического представления является делом вкуса, в данном случае, одно из них явно лучше другого, поскольку одно наводит на мысль о правильном отношении, а другое может привести к путанице. Стрелка - это не просто произвольная пиктограмма, она указывает на одностороннюю связь между своими двумя концами. В данном случае:
[x]. Всякий экземпляр наследника можно рассматривать как экземпляр родителя, а обратное неверно.
[x]. В тексте наследника всегда упоминается его родитель, но не наоборот. Это, на самом деле, является важным свойством ОО-метода, вытекающим из принципа Открыт-Закрыт, согласно которому класс не "знает" списка своих наследников и других собственных потомков.
Хотя у нас нет жесткого правила, определяющего для достаточно сложных систем размещение классов на диаграммах наследования, мы будем, по возможности, помещать класс выше его наследника.
Наследование инварианта
Хотелось бы указать инвариант класса RECTANGLE, который говорил бы, что число сторон прямоугольника равно четырем и что длины сторон последовательно равны side1, side2, side1 и side2.