Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP / books / Osnovi objektno-orientirovannogo programmirovaniya.pdf
Скачиваний:
63
Добавлен:
03.03.2016
Размер:
9.04 Mб
Скачать

Типизация при наследовании

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

Согласованность типов

Наследование согласовано с системой типов. Основные правила легко объяснить на приведенном выше примере. Предположим, что имеются следующие объявления:

p: POLYGON

r: RECTANGLE

Выделим в приведенной выше иерархии нужный фрагмент (рис. 14.6). Тогда законны следующие выражения:

*p.perimeter: никаких проблем, поскольку perimeter определен для многоугольников;

*p.vertices, p.translate (...), p.rotate (...) с корректными аргументами;

*r.diagonal, r.side1, r.side2: эти три компонента объявлены на уровне RECTANGLE или

QUADRANGLE ;

*r.vertices, r.translate (...), r.rotate (...): эти компоненты объявлены на уровне POLYGON или еще выше и поэтому применимы к прямоугольникам, наследующим все компоненты многоугольников;

*r.perimeter: то же, что и в предыдущем случае. Но у вызываемой здесь функции имеется новое определение в классе RECTANGLE, так что она отличается от функции с тем же именем из класса POLYGON.

Рис. 14.6. Фрагмент иерархии геометрических фигур А следующие вызовы компонентов незаконны, так как эти компоненты недоступны на

уровне многоугольника:

p.side1

p.side2

p.diagonal

Это рассмотрение основано на первом фундаментальном правиле типизации:

Правило Вызова Компонентов

Если тип сущности x основан на классе С, то в вызове компонента x.f сам компонент f должен быть определен в одном из предков С.

Напомним, что класс С является собственным предком. Фраза "тип сущности x основан на классе С " напоминает, что для классов, порожденных из родовых, тип может включать не только имя класса: LINKED_LIST [INTEGER]. Но базовый класс для типа - это LINKED_LIST, так что родовой параметр никак не участвует в нашем правиле.

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

Статическая типизация - это один из главных ресурсов ОО-технологии для достижения объявленной в 1-ой лекции цели - надежности ПО.

|Уже отмечалось, что не все подходы к построению ОО-ПО имеют статическую типизацию. Наиболее известным представителем языков с динамической типизацией является Smalltalk, в

котором не действует статическое правило вызова, но допускается, чтобы вычисление аварийно завершалось в случае возникновения ошибки: "сообщение не понятно". В лекции, посвященной типизации, будет приведено сравнение разных подходов. |

Пределы полиморфизма

Неограниченный полиморфизм был бы несовместим со статическим понятием типа. Допустимость полиморфных операций определяется наследственностью.

Все примеры полиморфных присваиваний, такие, как p := r и p := t, в качестве типа источника используют потомков класса-цели. Скажем, что в таком случае тип источника согласован с классом цели. Например, SQUARE согласован с RECTANGLE и с POLYGON, но не с TRIANGLE. Чтобы уточнить это понятие, дадим формальное определение:

Определение: согласованность

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

Почему недостаточно понятия потомка в этом определении? Причина снова в том, что допускается порождение из родовых классов, поэтому приходится различать типы и классы. Для каждого типа имеется базовый класс, который при отсутствии порождения совпадает с самим типом (например, POLYGON является базовым для себя). При этом для универсально порожденного класса базовым является универсальный класс с опущенными родовыми параметрами. Например, для класса LIST [POLYGON] базовым будет класс LIST. Вторая часть определения говорит о том, что B [Y] будет согласован с A [X], если B является потомком A, а Y - потомком X.

Заметим, что поскольку каждый класс является собственным потомком, то каждый тип согласован сам с собой.

При таком обобщении понятия потомка получаем второе важное правило типизации:

Правило согласования типов

Присоединение к источнику y цели x (т. е. присваивание x:=y или использование y в качестве фактического параметра в вызове процедуры с соответствующим формальным параметром x ) допустимо только тогда, когда тип y согласован с типом x.

Правило согласования типов выражает тот факт, что специальное можно присваивать общему, но не наоборот. Поэтому присваивание p := r допустимо, а r := p нет.

|Это правило можно проиллюстрировать следующим образом. Предположим, что я настолько ненормален, что послал в компанию Любимцы-По-Почте заказ на "Animal" ("Животное"). В этом случае, что бы я ни получил: собаку, божью коровку или дельфинакасатку, у меня не будет права пожаловаться. (Предполагается, что DOG и все прочие являются потомками класса ANIMAL ). Но если я заказал собаку, а почтальон принес мне утром коробку с надписью ANIMAL, или, например, MAMMAL (млекопитающее), то я имею право вернуть ее отправителю, даже если из нее доносится недвусмысленный лай и тявканье. Поскольку мой заказ не был исполнен в соответствии со спецификацией, я ничего не должен фирме Любимцы- По-Почте. |

Экземпляры

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

Определение: прямой экземпляр, экземпляр

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

Экземпляр C - это прямой экземпляр потомка C.

Из последней части этого определения следует, что прямой экземпляр класса C является также экземпляром C, так как класс входит во множество своих потомков.

Таким образом, выполнение фрагмента:

p1, p2: POLYGON; r: RECTANGLE

...

create p1 ...; create r ...; p2 := r

создаст два экземпляра класса POLYGON, но лишь один прямой экземпляр (тот, который присоединен к p1 ). Другой объект, на который указывают p2 и r, является прямым экземпляром класса RECTANGLE, а следовательно, экземпляром обоих классов POLYGON и RECTANGLE.

Хотя понятия прямого экземпляра и экземпляра определены выше для классов, они естественно распространяются на любые типы (с базовым классом и возможными родовыми параметрами).

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

Статико-динамическая согласованность типов

Сущность типа T может во время исполнения прикрепляться только к экземплярам класса

T.

Статический тип, динамический тип

Название последнего свойства предполагает различение "статического типа" и "динамического типа". Тип, который используется при объявлении некоторого элемента, является статическим типом соответствующей ссылки. Если во время выполнения эта ссылка присоединяется к объекту некоторого типа, то этот тип становится динамическим типом этой ссылки.

Таким образом, при объявлении p: POLYGON статический тип ссылки, обозначенной p, есть POLYGON, после выполнения create p динамическим типом этой ссылки также является POLYGON, а после присваивания p := r, где r имеет тип RECTANGLE и не пусто, динамическим типом становится RECTANGLE.

Правило согласования типов утверждает, что динамический тип всегда должен соответствовать статическому типу.

Чтобы избежать путаницы напомним, что мы имеем дело с тремя уровнями: сущность - это

Соседние файлы в папке books