Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
x = y
истинно тогда и только тогда, когда обе ссылки пусты или присоединены к одному и тому же объекту. Противоположный оператор "не эквивалентно" записывается как /=.
Выражение:
r = a.loved_one
истинно в ситуации, представленной на рис.8.13 и ложно для ситуации рис.8.12 .
Заметьте, в операциях эквивалентности сравниваются ссылки, а не объекты, к которым они присоединены. Так что если две ссылки присоединены к разным объектам, результатом операции эквивалентности будет false, даже если объекты имеют все поля с одинаковыми значениями. Операции, сравнивающие объекты, а не ссылки, будут введены позднее.
Значение void
Получить пустую ссылку достаточно просто - при инициализации по умолчанию все ссылки пусты. Однако удобно иметь специальное имя для ссылки, доступной в любом контексте и имеющей значение void . Предопределенный компонент
Void
играет эту роль.
Обычно компонент Void используется в тестах, проверяющих пустоту ссылок:
if x = Void then ...
и для того, чтобы присвоить некоторой ссылке это значение:
x := Void
В результате последнего присваивания происходит отсоединение ссылки от объекта, и она становится пустой. Эта ситуация показана на следующем рисунке:
Рис. 8.14. Отсоединение ссылки от объекта
Присваивание Void ссылке не оказывает никакого влияния на объект, ранее присоединенный к ссылке, - разрывается только связь между ссылкой и объектом. Было бы некорректно рассматривать эту операцию как освобождение памяти, так как другие ссылки могут продолжать быть связанными с объектом (на рисунке x может быть отсоединено от объекта O1, но другие ссылки могут быть еще присоединены к нему). Об управлении памятью смотри следующую лекцию.Клонирование и сравнение объектов
Ссылочное присваивание приводит к тому, что две или несколько ссылок присоединяются к одному объекту. Иногда необходима другая форма присваивания, в результате которой мы хотим получить не копию ссылки, а копию объекта. Эта цель достигается при вызове функции клонирования clone.
Если y присоединено к объекту OY, выражение
clone (y)
означает создание нового объекта OX , такого, что он имеет те же поля, что и OY, и все соответствующие поля имеют идентичные значения. Если y равно void, то значение clone (y) также void.
Скопировать присоединенный к y объект и связать копию со ссылкой x позволяет присваивание:
[1]
x := clone (y)
Вот иллюстрация этого механизма:
Рис. 8.15. Клонирование объекта
Наряду со сравнением ссылок необходим механизм, позволяющий сравнивать объекты. Этой цели служит функция equal . Вызов:
equal (x, y)
возвращает значение true, если и только если x и y оба имеют значение void или присоединены к двум объектам с идентичными полями. После выполнения присваивания с клонированием [1], состояние, непосредственно следующее за присваиванием, удовлетворяет equal (x, y).
Возможно, вы удивляетесь, почему у функции clone есть аргумент, а у функции equal - их два. Для ОО-стиля характерен квалифицируемый вызов в форме: y.twin и x.is_equal (y). Ответ появится в разделе обсуждения, но это будет еще не скоро, так что попытайтесь догадаться сами.Копирование объектов
Функция clone создает новую копию существующего объекта. Иногда целевой объект уже существует, и все, что необходимо, это скопировать значения полей. Процедура copy выполняет эту работу. Она вызывается обычным образом:
x.copy (y)
Сущности x и y должны быть одного и того же типа; эффект от выполнения - копирование полей объекта, присоединенного к y, в соответствующие поля объекта, присоединенного к x.
Как и во всех вызовах компонента, вызов copy требует, чтобы целевой объект x был не пуст. Дополнительно требуется, чтобы и y был не пуст. Эта неспособность иметь дело с пустыми ссылками отличает copy от clone.
Требование не пустоты y настолько важно, что должен существовать способ для его формального выражения. Фактически речь идет о более общей проблеме: как программа может задать предусловия на аргументы, передаваемые клиентом при ее вызове. Такие предусловия, являясь частным случаем общего понятия "утверждение" в деталях будут обсуждаться в последующих лекциях. Аналогично, нам хотелось бы уметь выражать в виде постусловия семантическое свойство, отмеченное выше, - результат выполнения clone удовлетворяет equal.Процедура copy может считаться более фундаментальной, чем функция clone в том смысле, что, по меньшей мере, для класса без процедуры создания можно выразить clone в терминах copy следующим образом:
clone (y: SOME_TYPE) is
-- Void если y равно void; иначе дублировать присоединенный к y объект
do
if y /= Void then
create Result --Правильно только в отсутствие процедур создания
Result.copy (y)
end
end
При вызове функции сущность Result автоматически инициализируется в соответствии с общими правилами для атрибутов. Вот почему нет необходимости в ветви else условного оператора: Result инициализируется значением Void , так что результатом функции будет void , если значение y это void.
Глубокое клонирование и сравнение
Формы копирования и сравнения, реализуемые подпрограммами clone, equal и copy , называются поверхностными, поскольку они работают с объектами только на первом уровне, никогда не пытаясь следовать вглубь по ссылкам. Возникает необходимость для глубоких вариантов этих операций, рекурсивно дублирующих полную структуру.
Для понимания разницы рассмотрим пример, показанный на рис.8.16. Предположим, что мы начинаем в начальном состоянии A, где сущностьaприсоединена к объекту O1.
Рис. 8.16. Различные формы присваивания и клонирования
Рассмотрим простое присваивание ссылки:
b := a
В состоянии B, показанном на рисунке, цель b в результате присваивания присоединена к объекту O1, к которому присоединен источник a. Никаких новых объектов не создается.
Далее рассмотрим операцию клонирования:
c := clone (a)
Эта инструкция, как показывает раздел C нашего рисунка, создает новый объект O4, с полями, идентичными полям объекта O1. Будут скопированы два ссылочных поля, и значения ссылок будут указывать на те же объекты O1 и O3, как и поля оригинального объекта O1. Но, заметьте, не происходит дублирования самого объекта O3, и никакого другого объекта помимо дублирования O1. По этой причине базисная операция clone называется поверхностным клонированием, - она останавливается на первом уровне объектной структуры.
Заметьте, при клонировании исчезли ссылки на себя. Ссылка landlord объекта O1 была присоединена к самому объекту O1. У объекта O4 это поле становится ссылкой на оригинал O1.В некоторых ситуациях вы, возможно, захотите пойти дальше и дублировать структуру рекурсивно без введения разделяемых ссылок. Функция глубокого клонирования deep_clone позволяет достичь цели. Процесс создания deep_clone (y) рекурсивно следует за всеми ссылочными полями, содержащимися в объекте, дублируя полную структуру. (Если y это void, то и результат будет также void.) Эта функция будет, конечно же, правильно обрабатывать циклические ссылочные структуры.
Нижняя часть на рисунке - раздел D - иллюстрирует выполнение этой операции: