![]() |
|||||||||
|---|---|---|---|---|---|---|---|---|---|
Динамические структуры: объекты Объекты
Когда слово "объект" используется в этой книге, то из контекста ясно, в общем или техническом смысле используется этот термин. В тех случаях, когда эту разницу необходимо подчеркнуть, используется уточнение - программный объект или внешний объект.
Класс C называется порождающим классом (generating class) или просто генератором (generator) объекта O. Заметьте, C- программный текст, а O - структура данных времени выполнения, появляющаяся в результате работы рассмотренных ниже механизмов создания объектов.
|
|||||||||
Разрешение присваиваний вида b1.page_count := 355 в C++ и Java отражает ограничения, возникающие при попытках внедрения объектной технологии в контекст языка C. Сами разработчики Java отмечают, что программист может испортить объект при наличии общедоступного поля, так как значение такого поля можно изменить путем непосредственного присваивания. Многие авторы языков вводят такую возможность, а затем предупреждают: "не делайте этого". Логичнее определить метод и нотацию, поддерживающие такие ограничения. |
В ОО-ПО классы без подпрограмм редко имеют практическое значение. Исключением являются ситуации, когда в родительских классах определяется набор атрибутов, а потомки содержат необходимые подпрограммы. Другим примером могут служить классы, представляющие внешние объекты, которые принципиально невозможно модифицировать, например данные от внешних датчиков в системе реального времени. Но на данном этапе такой подход полезен для понимания основных концепций, подпрограммы будут добавлены позже.
Писатели
Используя указанные выше типы, определим класс WRITER для описания автора книги:
class WRITER feature
name, real_name: STRING
birth_year, death_year: INTEGER
end
Ссылки
Чаще всего нам необходимы объекты с полями, представляющими другие объекты. Например, книга имеет автора, который представлен экземпляром класса WRITER.
Можно ввести понятие подобъекта. В новой версии класса BOOK2 его экземпляры содержат поле, являющееся объектом - экземпляром класса WRITER.
Такое понятие подобъекта, несомненно, полезно, и далее в этой лекции будет показано, как создавать соответствующие классы.
Но это не совсем то, что необходимо. В каждом экземпляре BOOK2 приходится дублировать информацию об одном и том же авторе в виде подобъекта. Причины неприемлемости такого решения:
Лучшим является решение, представленное на. Оно основано на новой версии класса, BOOK3.
Каждый экземпляр BOOK3 в поле author содержит ссылку (reference) на объект типа WRITER. Нетрудно дать точное определение.
Определение: ссылка
Ссылка это значение времени выполнения. Она может быть пустой (void) или присоединенной (attached).
Присоединенная ссылка однозначно идентифицирует объект (присоединена к конкретному объекту).
Наоба поля author экземпляров BOOK3 присоединены к одному экземпляру WRITER. Здесь и далее ссылки, присоединенные к объектам, обозначаются стрелками. На следующем рисунке используется графическое обозначение пустой ссылки, которая может обозначать неизвестного автора.
Определение ссылки не подразумевает конкретной аппаратно-программной реализации. Если ссылка не пуста, то она идентифицирует объект и может рассматриваться как абстрактное имя объекта.
Концепция ссылки должна, безусловно, иметь аналог при реализации. Программирование на уровне машинного кода использует адресацию, многие языки программирования содержат понятие указателя. Понятие ссылки является более абстрактным. Хотя ссылка, в конечном итоге, может быть представлена адресом, не следует из этого исходить. Ссылка может содержать адрес наряду с другой информацией.
Отличие ссылок от указателей выражается в том, что они типизированы. Они напоминают типизированные указатели в Pascal и Ada (но не в C). Это означает, что данная ссылка может быть связана только с объектами определенных типов. По аналогии с обычной жизнью - код города имеет смысл только при наборе телефонных номеров. Он может выглядеть как обычное целое, но никому не придет в голову суммировать коды.
Идентичность объектов
Понятие ссылки приводит к концепции идентичности объектов. Каждый объект, созданный в процессе выполнения ОО-системы, уникален и идентифицируется независимо от значений его полей. Возможны две ситуации:
Эти наблюдения свидетельствуют о неоднозначности высказывания "a обозначает тот же объект, что и b". Можно подразумевать различные объекты с одинаковыми данными (I1) или состояния одного и того же объекта до и после изменения значений полей (I2). Мы будем использовать второе толкование и считать, что значения полей заданного объекта могут изменяться в процессе выполнения, а он остается "тем же самым объектом". В случае (I1) будем говорить о равных (но различных) объектах, точное определение понятия равенства будет дано позже.
В соответствии с определением I2 можно сказать, что поля объекта могут изменяться и это не будет ошибкой. Термин "поле" обозначает одно из значений, составляющих объект, а не соответствующий идентификатор поля - имя одного из атрибутов порождающего класса. Каждому атрибуту класса соответствует поле объекта (1832 для атрибута date класса BOOK3 на). Атрибуты неизменны в процессе выполнения, как неизменно и деление объекта на поля, а значения полей меняться могут. Любой экземпляр BOOK3 будет всегда содержать четыре поля, соответствующие атрибутам title, date, page_count, author. Значения этих полей могут меняться у каждого экземпляра. |
Изучение того, как сделать объекты сохранямыми (persistent), заставит нас продолжить изучение свойств идентичности объектов. (См. "Идентичность объектов",курса "Основы объектно-ориентированного проектирования")
Класс BOOK1 содержал атрибуты только базовых типов, его вариант BOOK3, содержит атрибут, представляющий ссылку на автора.
class BOOK3 feature
title: STRING
date, page_count: INTEGER
author: WRITER -- Новый атрибут.
end
Объявленный тип дополнительного атрибута author это просто имя соответствующего класса: WRITER. Это будет общим правилом: если имеется стандартное объявление класса
class C feature ... end
то объявление некоторой сущности типа C
x: C
обозначает значения, являющиеся ссылками на потенциальные объекты типа C. Такое соглашение, использующее ссылки, обеспечивает большую гибкость и приемлемо в большинстве случаев. Подробное обсуждение этого правила и других возможных решений содержится в последнем разделе данной лекции.
Ссылка на себя
Ничто не препятствует объекту O1 в определенный момент выполнения системы содержать ссылку, присоединенную к самому O1. Такая ссылка на себя может быть косвенной. В ситуации наобъект, имеющий значением поля name: "Almaviva", сам является своим лендлордом (прямая циклическая ссылка). Фигаро любит Сюзанну, которая любит Фигаро (косвенная циклическая ссылка).
Такие циклы в динамических структурах возможны, только если клиентские отношения между соответствующими классами также содержат прямые или косвенные циклы. Объявление класса
class PERSON1 feature
name: STRING
loved_one, landlord: PERSON1
end
содержит прямой цикл (PERSON1 - клиент PERSON1).
Обратное утверждение неверно - присутствие цикла в объявлении класса не означает, что циклы обязательно появятся в структурах времени выполнения. Можно объявить класс
class PERSON2 feature
mother, father: PERSON2
end
Класс является собственным клиентом. Однако если он моделирует соответствующие именам атрибутов отношения между людьми, то структуры времени выполнения никогда не будут содержать циклов, поскольку ни один человек не может быть собственным родителем или предком.
Взгляд на структуру объектов периода выполнения
На основе предшествующего рассмотрения выясняется в первом приближении структура ОО-системы в процессе выполнения.
Система состоит из нескольких объектов с различными полями. Некоторые поля содержат значения базовых типов, а другие являются пустыми или присоединенными ссылками на другие объекты. Каждый объект является экземпляром некоторого типа, основанного на классе (на рисунке тип указывается под объектом). Некоторые типы представлены единственным экземпляром, но гораздо чаще присутствует несколько экземпляров одного типа. На тип TYPE1 представлен двумя экземплярами, остальные - единственным. Некоторые объекты содержат поля только ссылочного типа (экземпляр TYPE4) или только базовых типов (экземпляр TYPE5). Могут присутствовать прямые или косвенные циклические ссылки (верхнее поле экземпляра TYPE2, по часовой стрелке от нижнего экземпляра TYPE1).
Подобная структура может показаться слишком запутанной. Впечатление от приведенной иллюстрации, демонстрирующей различные возможности, можно выразить выражением: "блюдо спагетти".
Это впечатление не совсем правильно. Впечатление простоты должен создавать программный текст, но не структура объектов периода выполнения. Текст отражает определенные отношения (такие как "любит", "имеет хозяина"). Конкретную структуру объектов периода выполнения можно назвать экземпляром таких отношений, она фиксирует связи между элементами данного набора объектов. Моделируемые отношения могут быть простыми, в то время как отношения индивидуумов конкретного множества объектов - достаточно сложными. Понятие "любит" очень просто, однако любовные отношения конкретных людей могут быть безнадежно запутаны.
Во время выполнения могут неизбежно возникать структуры, содержащие много объектов и имеющие запутанную структуру ссылок. Хорошая среда разработки должна предоставлять средства анализа объектных структур для тестирования и отладки.
Сложность динамических структур не должна влиять на статическую картину. Необходимо стараться сохранить набор классов и их отношения настолько простыми, насколько это возможно.
Тот факт, что простым моделям могут соответствовать сложные структуры данных, частично отражает мощь наших компьютеров. Короткий исходный текст может описывать огромные вычисления. Простая ОО-система может порождать в процессе выполнения миллионы объектов, связанных большим числом ссылок. Важнейшей целью программной инженерии является сохранение простоты ПО, даже когда экземпляры объектов такой простотой не обладают.
Рассмотренные приемы позволяют продвинуться в понимании возможностей ОО-подхода как средства моделирования. Важно, в частности, прояснить два аспекта: рассмотреть различные миры, связанные с разработкой ПО и отношения между ПО и внешней реальностью.
Четыре мира программной разработки
Из предшествующей дискуссии следует, что когда мы говорим об ОО-разработке, следует различать четыре отдельных мира:
Соотношения между этими мирами представлены на.
И на программном и на внешнем уровне (нижняя и верхняя части рисунка) важно разграничить общие понятия и их конкретные реализации (классы и абстрактные отношения слева, объекты и отношения экземпляров справа). Данный момент уже обсуждался в дискуссии о сравнительной роли классов и объектов в предыдущей лекции. Применительно к отношениям необходимо отличать абстрактные отношения loved_one от множества связей loved_one, существующих между элементами конкретного множества объектов.
Это различие невыразимо ни в стандартных математических определениях понятия "отношение", ни в программистской терминологии, например в теории реляционных баз данных. Если ограничиться бинарными отношениями, то и в математике и в теории баз данных отношение определяется как множество пар в форме <x, y>, где x и y являются элементами заданных множеств TX и TY. В терминах программирования все x относятся к типу TX, а все y - к типу TY. Будучи пригодными для математиков, эти определения не подходят для целей моделирования, поскольку не позволяют различать абстрактные отношения и отношения конкретных экземпляров. При моделировании системы отношение "любит" имеет свои общие и абстрактные свойства, совершенно не зависящие от записи того, кто кого любит в конкретной группе людей в некоторый момент времени.
Это обсуждение будет продолжено в, когда будут рассматриваться преобразования между абстрактными и конкретными объектами, и будет дано имя вертикальным стрелкам предыдущего рисунка - функция абстракции. (См. "Функции абстракции",) |
Реальность: "седьмая вода на киселе"
Предшествующее обсуждение не содержит ссылок на "реальный мир", - вместо этого используется термин "моделируемая система".
Такое разграничение проводится не всегда. Во многих дискуссиях используется выражение "моделирование реального мира"; аналогичные высказывания содержат и книги по ОО-анализу. Однако говорить о "реальности" применительно к программной системе ошибочно, по крайней мере, по четырем причинам.
Во-первых, реальность отражается в глазах очевидца. Не впадая в профессиональный шовинизм, программист всегда вправе спросить своих заказчиков, почему их системы более реальны, чем его. Возьмите программу, выполняющую математические вычисления - проверку гипотезы четырех красок в теории графов, интегрирование дифференциальных уравнений или решение геометрических проблем на четырехмерной римановой поверхности. Нужно ли нам, программистам, спорить с друзьями (математиками, заказчиками) о том, чьи искусственные объекты - артефакты более реальны - фрагменты программного кода или полное подпространство отрицательной кривизны? (См. также)
Во-вторых, понятие реального мира рушится в нередких ситуациях, когда ПО предназначено для разрешения проблем ПО. Рассмотрим компилятор C, написанный на Pascal. Для него "реальными" объектами являются программы на C. Насколько эти программы более реальны, чем сам компилятор? Это наблюдение применимо и к другим системам, работающим с объектами, существующими только в компьютере. (См.)
Третье соображение обобщает второе. В сегодняшнем информационном мире компьютеры стали частью реальности. На заре появления компьютеров можно было говорить, что создаваемая программная система моделирует реальную систему. Предприятие приобретало компьютеры для автоматизации бизнес процессов. При описании процессов современного банка его ПО является фундаментальной частью банковской системы. Ситуация аналогична квантовой физике, где невозможно отделить измерение от измеряемого механизма. Термин "виртуальная реальность" в какой-то мере отражает данную ситуацию. Программные продукты не менее реальны, чем те, что приходят из внешнего мира. Во всех таких ситуациях программная система пересекается с реальностью, отчего возникает положительная обратная связь, когда работа существующей системы приводит к новым и важным изменениям самой модели, приводя к изменениям программной системы.
Последний довод наиболее фундаментален. Программная система не является моделью реальности. В лучшем случае это модель модели некоторой части некоторой реальности. Система мониторинга пациента больницы не является моделью больницы, но реализацией конкретной точки зрения на некоторые аспекты работы больницы. Это модель модели некоторой части реальности больницы. Астрономическая программа это не модель вселенной, а всего лишь программная модель чьей-то модели некоторых свойств некоторой части вселенной. Финансовая информационная система не является моделью фондового рынка. Это программная реализация модели, разработанной конкретной компанией для описания тех аспектов фондового рынка, которые соответствуют целям данной компании.
Абстрактные типы данных, лежащие в основе ОО-метода, помогают понять, почему не следует придерживаться широко распространенной, но иллюзорной точкой зрения, что мы имеем дело с "реальным миром". Первый шаг к объектной ориентации, выражаемый теорией АТД, состоит в отказе от реальности ради менее грандиозного, но более аппетитного яства, - представляющего множество абстракций, характеризующих операции, доступные клиентам, и их формальные свойства. (На этом построен девиз модельера АТД - не говорите мне, кто вы, скажите, чем вы обладаете.) Мы никогда не претендуем на то, что рассмотрели все возможные операции и свойства: мы выбрали некоторые из них, подходящие для наших целей и отбросили остальные. Моделирование означает отсекание лишнего.
В идеальном случае программная система приходится соответствующей реальности лишь "седьмой водицей на киселе" (cousin twice removed).
Вернемся к более приземленным проблемам и рассмотрим, как программные системы работают с объектами, как создают и используют гибкие структуры данных.
Динамическое создание и повторное связывание
Что не было показано при описании структуры объектов периода выполнения, так это в высшей степени динамичная природа настоящей ОО-модели. Статическая и ориентированная на стеки политика управления объектами характерна для языков уровня Fortran и Pascal соответственно. Противоположной является политика в настоящем ОО-окружении, позволяющая создавать объекты в период выполнения, когда в них возникает потребность. Какому образцу (типу) соответствуют создаваемые объекты, как правило, невозможно предсказать при статической проверке программного текста.
В начальном состоянии, как описано в предыдущей лекции, создается единственный корневой объект. Затем система повторно выполняет операции создания новых объектов, связывает изначально пустые ссылки с этими объектами, делает ранее присоединенные ссылки пустыми или присоединяет их к другим объектам. Динамическая и непредсказуемая природа этих операций обеспечивает гибкий подход и позволяет поддерживать динамические структуры данных, необходимые для реализации сложных алгоритмов и моделирования быстро меняющихся свойств внешних систем.
Следующий раздел посвящен механизмам, необходимым для создания объектов и манипулирования их полями, в частности, ссылками.
Инструкция создания
Рассмотрим создание экземпляра класса BOOK3. Это возможно только с помощью подпрограммы класса, являющегося клиентом BOOK3, как, например:
class QUOTATION feature
source: BOOK3
page: INTEGER
make_book is
-- Создание объекта BOOK3 и присоединение его к source.
do
... См. ниже ...
end
end
Этот класс описывает цитирование книги в других публикациях. Он содержит два поля: ссылку на цитируемую книгу и число страниц, содержащих ссылки на нее.
Механизм создания экземпляра QUOTATION (скоро он будет рассмотрен) предусматривает инициализацию всех его полей. Правило инициализации по умолчанию определяет, что любое ссылочное поле (в данном примере - поле, соответствующее атрибуту source) после инициализации должно содержать пустую ссылку. Другими словами, создание объекта типа QUOTATION не сопровождается созданием объекта типа BOOK3.
Ссылка остается пустой, пока над ней не будут выполнены некоторые действия, - таково общее правило. Изменить значение ссылки можно, создав, например, новый объект. В процедуре make_book это делается следующим образом:
make_book is
-- Создание объекта BOOK3 и присоединение его к source.
do
create source
end
Это иллюстрация простейшей формы инструкции создания: create x , где x - атрибут охватывающего (enclosing) класса или, как будет показано позже, локальная сущность охватывающей подпрограммы. Далее эта базовая нотация будет расширена.
Сущность x, именованная в инструкции (в данном примере source), называется целью (target) инструкции создания.
Данная форма известна как "базовая инструкция создания". Другая форма, включающая вызов процедуры класса, скоро появится. Вот точное определение действия базовой инструкции создания:
Результат базовой инструкции создания
Эффект инструкции создания вида create x , где тип цели x является ссылочным типом, основанном на классе C, состоит в выполнение трех следующих действий:
На этапе C1 создается экземпляр C. На этапе C2 устанавливаются предопределенные значения всех полей, зависящие от типа соответствующего атрибута:
Значения по умолчанию при инициализации
Для ссылок значение по умолчанию - пустая ссылка.
Для полей BOOLEAN значение по умолчанию - False.
Для полей CHARACTER значение по умолчанию - символ null.
Для чисел (типов INTEGER, REAL или DOUBLE) значение по умолчанию - ноль в соответствующем данному типу представлении.
Итак, для цели source типа BOOK3 в соответствии с объявлением класса
class BOOK3 feature
title: STRING
date, page_count: INTEGER
author: WRITER
end
результатом инструкции создания create source, выполняемой при вызове процедуры make_book класса QUOTATION, будет объект изображенный на.
После инициализации значения целочисленных полей равны нулю. Ссылочное поле author и поле title типа STRING, содержат пустые ссылки. Тип STRING, о котором ничего не говорится в правилах инициализации, двойственен, - фактически являясь ссылочным типом, он рассматривается во многих ситуациях как базовый тип.
Важно проследить за последовательностью происходящих событий. Для рассмотренного выше экземпляра BOOK3 происходит следующее:
Правомерен вопрос - как будет создан сам Q_OBJ (шаг B1)? Это, оставляя проблему, отодвигает ее вглубь. Но к этому моменту мы уже знаем ответ на этот вопрос: все возвращается к первопричине - Большому Взрыву. Для выполнения системы необходимо снабдить ее корневым классом и процедурой этого класса, названной процедурой создания. В начале выполнения автоматически создается один объект - корневой объект - экземпляр корневого класса. Корневой объект является единственным объектом, не создаваемым инструкциями программного текста; он приходит извне, как objectus ex machine (объект от машины). Начав с одного, провидением посланного объекта, далее уже программа может создавать объекты нормальным путем через подпрограммы, выполняющие инструкции создания. Первой выполняемой подпрограммой является процедура создания, автоматически применяемая к корневому объекту. Не всегда, но чаще всего она содержит по крайней мере одну инструкцию создания, что в предыдущей лекции называлось началом грандиозного фейерверка, процесса, создающего столько новых объектов, сколько нужно текущему выполнению.
Для чего необходимо явное создание объектов?
Объекты создаются явным образом. Объявление сущности
b: BOOK3
не влечет за собой создание объекта во время выполнения, это происходит, когда некий элемент системы выполнит операцию
create b
Это может показаться удивительным. Разве объявления b недостаточно для создания объекта? Что хорошего в объявлении, если объект не создается?
Достаточно минуты размышления для понимания того, что разделение объявления и создания объекта является единственно разумным решением.
Первый аргумент - reductio ad absurdum (доведение до абсурда). Предположим, что начата обработка объявления и немедленно создается соответствующий объект. Но это экземпляр класса BOOK3, имеющий атрибут author ссылочного типа WRITER, значит поле author - ссылка, для которой опять нужно создавать объект. Этот объект вновь содержит ссылочные поля, требуется опять делать то же самое и начинается длинный путь рекурсивного создания объектов.
Этот аргумент еще более убедителен для таких классов как PERSON1, содержащих ссылки на себя:
class PERSON1 feature
name: STRING
loved_one, landlord: PERSON1
end
Появление каждого экземпляра PERSON1 повлечет за собой создание двух других таких объектов (соответствующих loved_one и landlord ) и начнется бесконечный цикл. Такие прямые или косвенные циклические ссылки не экзотика - они часто встречаются и необходимы.
Другой аргумент следует из обсуждения роли объектной технологии как мощного метода моделирования. Если для каждого ссылочного поля будет создаваться новый объект, то не было бы возможности выделить пустые ссылки и множественные ссылки на один и тот же объект. И то, и другое необходимо для реалистичного моделирования систем:
Механизм управления объектами никогда не присоединяет ссылку неявно. Он создает объекты через инструкции создания (или операции клонирования, тоже явные), инициализируя их ссылочные поля пустыми ссылками. Эти поля, в свою очередь, могут стать присоединенными к объектам, только в результате явных операций над этими полями.
В дискуссии о наследовании будет показано, что инструкция создания может использовать синтаксис create {T}x для создания объекта, чей тип T является наследником типа объявленного для x. |
Все до сих пор рассмотренные инструкции создания основывались на инициализации по умолчанию. В некоторых случаях инициализация, определенная в языке, может нас не устраивать - хотелось бы обеспечить создаваемый объект специфической информацией. В этом предназначение процедур создания.
Перекрытие инициализации по умолчанию
Для использования инициализации, отличной от предопределенной умолчанием, необходимо класс снабдить одной или несколькими процедурами создания. Такие процедуры должны быть перечислены в предложении, начинающимся ключевым словом creation в начале класса перед первым предложением feature. Схема такова:
indexing
...
class C creation
p1, p2, ...
feature
... Объявления компонент, включая реализацию процедур p1, p2, ...
end
Совет, отражающий стиль: в случае класса с единственной процедурой создания - для нее рекомендуется имя make. Для классов с двумя и более процедурами создания желателен префикс make_, за которым следует квалификатор, как в следующем примере POINT. (См. "Правильный выбор имен",курса "Основы объектно-ориентированного проектирования") |
Соответствующая инструкция создания в этих случаях имеет другую форму:
create x.p (...)
где p одна из процедур создания перечисленных в разделе creation, и в круглых скобках (...) перечисляются фактические аргументы p. Результатом является создание объекта с использованием значений по умолчанию, как и ранее, а затем вызов p с заданными аргументами. Такая инструкция является комбинацией инструкции создания и вызова процедуры и называется порождающим вызовом (creation call). (Оригинальная версия класса POINT приведена в
В качестве примера добавим две процедуры создания в класс POINT, что позволит клиентам при создании новой точки указывать ее начальные координаты - декартовы или полярные. Введем процедуры создания: make_cartesian и make_polar. Вот схема:
class POINT1 creation
make_cartesian, make_polar
feature
... Компоненты из предыдущей версии класса:
x, y, ro, theta, translate, scale, ...
feature {NONE} - Этот вариант экспорта рассмотрен ниже.
make_cartesian (a, b: REAL) is
-- Инициализация точки с декартовыми координатами a и b.
do
x := a; y := b
end
make_ polar (r, t: REAL) is
-- Инициализация точки с полярными координатами r и t.
do
x := r * cos (t); y := r * sin (t)
end
end
Для такого класса клиент будет создавать точки инструкциями вида:
create my_point.make_cartesian (0, 1)
create my_point.make_polar (1, Pi/2)
В обоих случаях создается точка с одинаковыми координатами в предположении, что константа Pi имеет общепринятый смысл. Вот правило, определяющее эффект порождающего вызова. Первые три пункта правила такие же, как и для базисной формы, приведенной ранее: