Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
Как мы видели при обсуждении перегрузки, сигнатура не является подходящим критерием распознавания. Например: конструкторы make_cartesian и make_polar имеют одинаковую сигнатуру, так что придется вводить искусственный аргумент в один из конструкторов, чтобы стало возможным отличить вызов нужного конструктора.
Наша техника кажется предпочтительнее во всех отношениях. Минимум усилий (никаких процедур создания), если инициализация по умолчанию применима; при желании позволяет предотвратить создание объектов клиентами; вводит столько процедур создания, сколько необходимо, не создавая никаких коллизий; каждая процедура создания имеет собственное имя, облегчая понимание ее предназначения, например make_polar .
Еще о ссылках
Модель периода выполнения определяет важную роль ссылок. Рассмотрим некоторые их свойства, в частности, понятие пустой (void) ссылки и связанные с ней проблемы.
Состояния ссылок
Ссылка может находиться в одном из двух состояний - она может быть пустой или присоединенной. Мы уже видели, что изначально ссылка всегда находится в состоянии void и может стать присоединенной благодаря созданию объекта. Вот как выглядит более полная картина, показывающая все возможности перехода между состояниями:
Рис. 8.11. Возможные состояния ссылки и переходы
Помимо создания, ссылка может изменять состояние в результате присваивания. Проверьте себя, понимаете ли вы разницу между тремя понятиями - объектом, ссылкой и сущностью:
[x]. "Объект" - это понятие периода выполнения; любой объект является экземпляром класса, создается во время выполнения системы и представляет собой набор полей.
[x]. "Ссылка" - это понятие периода выполнения. Значение ссылки либо void , либо она присоединена к объекту. Точное определение "присоединения" уже появлялось. Присоединенная ссылка однозначно идентифицирует объект.
[x]. "Сущность" - это статическое понятие, применимое к программному тексту, - это идентификатор в тексте класса, представляющий значение или множество значений в период выполнения. Сущностями являются обычные переменные, именованные константы, аргументы подпрограмм и результаты функций.
Если b - сущность ссылочного типа, то ее значением в период выполнения является ссылка, которая может быть присоединена к объекту O. В этом случае говорим, что сущность b присоединена к O.
Вызовы и пустые ссылки
В большинстве случаев мы ожидаем, что ссылка присоединена к объекту, хотя допустимо иметь и пустые ссылки. Ссылки void играют важную роль в ОО-модели вычислений. В предыдущей лекции подробно разбиралась фундаментальная операция ОО-модели - вызов компонента - применение к экземпляру класса компонента этого класса. Вот как это пишется:
some_entity.some_feature (arg1, ...)
Для корректного выполнения вызова сущность some_entity должна быть присоединена к нужному целевому объекту. Если случится, что some_entity ссылочного типа и имеет значение void , то вызов не может быть обработан, так как необходим целевой объект.
ОО-система никогда не должна в момент выполнения вызывать компонент с целевым объектом void . Результатом подобного вызова будет исключение (exception).(Исключения и их обработка будут изучаться в лекции 12)
Было бы прекрасно, если бы компилятор проверял программный текст и гарантировал, что подобные события не встретятся в период выполнения, точно также как он проверяет отсутствие несовместимости типов, используя соответствующие правила типизации. К сожалению, такая цель недостижима для компиляторов, если только не накладывать жестких ограничений на язык. Так что ответственность за то, чтобы все вызовы имели присоединенный целевой объект, возлагается на разработчика. Конечно, есть простой способ - окружать все вызовы тестом:
if "x не void" then
x.f (...)
else
...
end
Этот прием не применим в качестве универсального требования, хотя и может использоваться, когда из контекста не следует, что целевой объект не будет пуст.
Вопрос о не пустоте ссылок является частью вопроса корректности ПО. Для проверки корректности системы необходимо проверить, что нет вызовов, применимых к void ссылкам, и что все утверждения (изучаемые в последующих лекциях) удовлетворяются в соответствующий момент выполнения. Проверка не пустоты, также как и проверка утверждений, могла бы проводиться специальным автоматом - верификатором, встроенным в компилятор или являющимся независимым средством. В отсутствие такого механизма подобные нарушения приведут к ошибке периода выполнения - исключению. Разработчики могут защитить ПО двумя путями:
[x]. В процессе разработки использовать все доступные приемы, позволяющие избежать ошибочных ситуаций, применяя, например, средства, позволяющие выполнять частичную проверку.
[x]. Если остаются малейшие сомнения, то поставлять ПО с механизмом обработки исключений.
Операции над ссылками
Мы уже знакомы с одним из способов изменения значения ссылки x: использование инструкции создания в форме create x , позволяющей создать новый объект и присоединить его к ссылке. Имеются и другие интересные операции, доступные при работе со ссылками.
Присоединение ссылки к объекту
Классы, появляющиеся в этой лекции, не имели подпрограмм - у них были только атрибуты. Как отмечалось, такие классы почти бесполезны, так как у них нет способа изменить значение атрибутов. Необходимы способы модификации ссылок, не использующие при этом инструкций в духе языков Pascal-C-Java-C++, подобных присваиванию: my_beloved.loved_one := me (напрямую изменяющих у объекта поле loved_one), что нарушает принцип скрытия информации и синтаксически некорректно в нашей нотации.
Для модификации полей объекта клиент обязан вызвать подпрограмму, специально поставляемую разработчиком класса для этих целей. Давайте включим в класс PERSON1 процедуру, позволяющую модифицировать поле loved_one. Вот результат:
class PERSON2 feature
name: STRING
loved_one, landlord: PERSON2
set_loved (l: PERSON2) is
-- Присоединить поле loved_one текущего объекта к объекту l.
do
loved_one := l
end
end
Процедура set_loved присваивает ссылочному полю loved_one текущего экземпляра PERSON2 значение другой ссылки l. Ссылочное присваивание (левая и правая части являются ссылками) присваивает значение источника (правой части) целевой ссылке (слева).
Эффект ссылочного присваивания очевиден: целевая ссылка становится присоединенной к объекту, к которому присоединен источник - или становится void, если такое значение имеет источник. Предположим, например, что мы начинаем с ситуации, изображенной на рис.8.12 , где поля landlord и loved_one всех изображенных объектов пока пусты.
Рис. 8.12. Перед присваиванием ссылке
Предположим, что выполняется вызов процедуры:
a.set_loved (r)
Сущность a присоединена к объекту O1, а сущность r - к O3. В результате выполнения процедуры set_loved выполнится присваивание:
loved_one := l
Здесь в роли текущего объекта выступает объект O1, сущности l и r имеют одинаковое значение - ссылки на объект O3. В результате изменится значение поля loved_one объекта O1 - ссылка присоединится к другому объекту O3, как показано на следующем рисунке:
Если бы r было пустой ссылкой, то такой же в результате присваивания стала бы и ссылка в поле loved_one объекта O1.
Рис. 8.13. После присваивания ссылки
Сравнение ссылок
Наряду с присваиванием возникает необходимость и в тесте - проверить, присоединены ли две ссылки к одному и тому же объекту. Для этого есть оператор эквивалентности =.
Если x и y - сущности ссылочного типа, то выражение:
x = y
истинно тогда и только тогда, когда обе ссылки пусты или присоединены к одному и тому же объекту. Противоположный оператор "не эквивалентно" записывается как /=.