Книга: Основы объектно-ориентированного программирования
Составные объекты и развернутые типы
Обсуждение структуры объектов времени выполнения показало важную роль ссылок. Для завершения картины необходимо выяснить, как работать со значениями, представляющими собой не ссылки на объекты, а непосредственно сами объекты.
Ссылок не достаточно
До сих пор все значения целочисленных, булевых и других аналогичных типов рассматривались как ссылки на объекты. Однако по двум причинам необходимы сущности, значениями которых являются объекты:
[x]. В предыдущей лекции была поставлена важная цель - построение полностью унифицированной системы типов. В этой схеме базовые типы (BOOLEAN, INTEGER и др.) обрабатываются аналогично типам, введенным разработчиком (POINT, BOOK и др.). Тем не менее, если используется сущность n типа INTEGER, то в большинстве случаев удобнее полагать, что значение n - целое число, а не ссылка на объект содержащий целое число. Это удобнее отчасти по соображениям эффективности. Понятно, что для размещения целочисленных объектов необходимо больше памяти, а на обработку косвенного доступа к ним - дополнительное время. Кроме того, концептуально целое число и ссылка на целое число - совершенно различные понятия. Этот довод важен, если нашей целью является построение точной модели.
[x]. Даже в случае сложных, определенных программистом объектов, может оказаться предпочтительным включение в объект O1 подобъекта O2, а не ссылки на внешний объект O2. Причиной такого подхода могут быть повышение эффективности, точное моделирование или и то, и другое.
Развернутые типы
Удовлетворить потребность в составных объектах очень просто. Пусть C- класс, определенный так, как это делалось до сих пор
class C feature
...
end
Класс C может использоваться в качестве типа. Любая сущность типа C является ссылкой. По этой причине C называется ссылочным типом (reference type).
Теперь предположим, что нам необходима сущность x, значение которой во время выполнения будет экземпляром C, а не ссылкой на такой экземпляр. Это достигается следующим объявлением x:
x : expanded C
Эта нотация использует новое ключевое слово expanded (развернутый). Нотация expanded C означает, что экземпляры этого типа в точности соответствуют экземплярам C. Единственное отличие от обычного объявления типа состоит в том, что сущности типа C обозначают ссылки, которые могут быть присоединены к экземплярам C, а сущности типа expanded C обозначают непосредственно экземпляры C.
Таким образом, к структуре, определенной в предыдущих разделах, добавлено понятие составного объекта (composite object). Объект O называется составным, если одно или более его полей являются объектами - подобъектами (subobjects) O . Следующий класс является примером описания составных объектов:
class COMPOSITE feature
ref: C
sub: expanded C
end
Класс COMPOSITE имеет два атрибута: ref, обозначающий ссылку, и sub, обозначающий подобъект. Вот как выглядит прямой экземпляр COMPOSITE.
Рис. 8.19. Составной объект с одним подобъектом
Поле ref является ссылкой, присоединенной к экземпляру C (возможно, пустой ссылкой). Поле sub содержит экземпляр C и не может быть пустым.
Удобно несколько расширить нотацию. Иногда при проектировании класса, например E, хотелось бы установить, что все экземпляры класса должны быть развернутыми. Чтобы сделать это требование явным, следует объявить класс в следующей форме:
expanded class E feature
... Далее все аналогично любому другому классу ...
end
Так определенный класс называется развернутым классом. Такое объявление класса никак не отражается на экземплярах класса, они остаются такими же, как если бы класс был объявлен просто class E. Но сущности типа E изменяются - теперь их значения не ссылки, а сами объекты. Как следствие этой новой возможности понятие развернутого типа включает два случая:
Определение: развернутый тип
Тип является развернутым в двух случаях:
Он задан в форме: expanded C
Он задан в форме E, где E - развернутый класс.
Объявление вида
x: expanded E
где E - развернутый класс, не будет ошибкой, поскольку эквивалентно
x: E
Таким образом, имеется два вида типов. Тип, не являющийся развернутым, является ссылочным типом. Эту терминологию можно использовать и для сущностей - ссылочные сущности и развернутые сущности. Аналогично и классы могут быть ссылочными и развернутыми.
Роль развернутых типов
Почему нам нужны развернутые типы? Они играют три важные роли:
[x]. улучшают эффективность;
[x]. обеспечивают лучшее моделирование;
[x]. поддерживают базисные типы в унифицированной ОО-системе типов.
Первое применение наиболее очевидно: без развернутых типов каждый раз необходимо использовать ссылки для описания составных объектов. Это означало бы при каждом обращении к подобъекту выполнения операции, называемой "разыменование" (dereferencing), что влекло бы к временным потерям. Помимо этого, есть и потери в памяти, поскольку нужно отводить память не только объектам, но и самим ссылкам.
Аргумент производительности, однако, не является ключевым. ОО-конструирование ПО зачастую рассматривается как моделирование. Для отражения реальности необходимо моделировать объект как составной, а не как объект со ссылками. Это концептуальная проблема, а не проблема реализации.
Рассмотрим два объявления атрибутов:
D1. ref: S
D2. exp: expanded S
Объявления появляются в классе C, предполагается также, что S это ссылочный класс. Объявление D1 отражает тот факт, что каждый экземпляр класса C "знает о" существовании некоторого экземпляра S (если только ref не является void). Объявление D2 более требовательное: оно устанавливает тот факт, что каждый экземпляр класса C "содержит" экземпляр S. Даже если не думать о проблемах реализации, следует понимать, что речь идет о двух разных отношениях.
Отношение "содержит", поддерживаемое развернутыми типами, не допускает никакого разделения встроенного объекта, в то время как отношение "знает о" допускает несколько ссылок, присоединенных к объекту.
Вот пример объявления класса:
class WORKSTATION feature
k: expanded KEYBOARD
c: expanded CPU
m: expanded MONITOR
n: NETWORK
...
end
Рабочая станция имеет клавиатуру, ЦПУ, монитор и подключена к сети. Клавиатура, ЦПУ и монитор являются частью данного компьютера и не могут разделяться двумя или несколькими рабочими станциями. Однако несколько рабочих станций подключены к одной и той же сети. Эти особенности проявляются в определении класса, использующем развернутые типы для первых трех атрибутов и ссылочный тип для атрибута "сеть".
Рис. 8.20. Отношения между объектами: «знает о» и «содержит»
Итак, концепция развернутого типа, появившаяся вначале как техника уровня реализации, фактически помогла описать некоторые из отношений, используемых при информационном моделировании. Отношение "содержит" и обратное к нему отношение "быть частью" являются центральными при построении моделей внешних систем; они появляются в методах анализа и при моделировании баз данных.
Третье важное приложение развернутых типов фактически является частным случаем второго. В предыдущей лекции подчеркивалась желательность унифицированной системы типов, включающей как встроенные, так и пользовательские типы. Пример REAL использовался, чтобы показать, как с помощью инфиксных и префиксных компонентов можно промоделировать понятие вещественного числа как класса. То же самое нетрудно проделать и для других базисных типов: BOOLEAN, CHARACTER, INTEGER, DOUBLE. Но проблема все же остается. Если классы рассматривать как ссылочные, то сущности базисных типов, такие как
r: REAL
будут в период выполнения ссылками на возможные объекты, содержащие значение (в данном случае REAL). Это неприемлемо: чтобы соответствовать общей практике программирования значение должно быть не ссылкой, а самим вещественным числом. Решение проблемы немедленно следует из обсуждения - класс REAL следует объявить как развернутый. Его объявление должно быть таким:
expanded class REAL feature
... Объявления компонент такие же как и ранее ...
end
Все другие базисные типы объявляются подобным образом как развернутые.
Агрегирование
В некоторых областях информатики - базах данных, моделировании, анализе требований - разработана классификация отношений, имеющих место между элементами моделируемой системы. В этих контекстах часто встречается отношение "агрегирования" (aggregation), выражающее тот факт, что каждый объект некоторого типа является агрегатом - содержит в своем составе ноль или более объектов, каждый из которых имеет свой собственный тип. Например: автомобиль является агрегатом, содержащим мотор, кузов и другие детали.
Развернутые типы обеспечивают эквивалентный механизм. Мы можем, например, объявить класс CAR с компонентами развернутых типов: expanded ENGINE и expanded BODY. Другой способ основан на том, что агрегирование представляется отношением "развернутый клиент". Говорят, что класс Cявляется развернутым клиентом класса S, если он содержит объявление компонента типа expanded S (или просто S, если S развернут). Одно из преимуществ такого модельного подхода в том, что развернутый клиент - это частный случай общего отношения "быть клиентом", так что можно использовать общие рамки и нотацию, комбинируя зависимости, подобные агрегированию с зависимостями, допускающими разделение. Примером могут служить с одной стороны - отношение между WORKSTATION и KEYBOARD, с другой - отношение между WORKSTATION и NETWORK.
Используя ОО-подход, можно избежать множественности отношений, используемых в литературе по информационному моделированию, - все покрывается двумя отношениями: клиент (развернутый или нет) и наследование.
Свойства развернутых типов
Рассмотрим развернутый тип E (в любой форме) и развернутую сущность x типа E.
Так как значение x это всегда объект, то он не может быть void. Так что булево выражение:
x = Void
будет всегда вырабатывать значение false, и вызов в форме x.some_ feature (arg1, ...) никогда не приведет к возбуждению исключения из-за void цели, что могло случиться для ссылочной сущности.
Пусть объект O является значением x. Как и в случае не пустой ссылки, говорят, что x присоединено к O. Итак, для любой сущности, значение которой не void, можем говорить о присоединенном объекте, независимо от типа - ссылочного или развернутого - сущности.
Что можно сказать о создании развернутых объектов? Инструкцию:
create x
можно применить к развернутому x. Для ссылки x эффект достигался за три шага: (C1) создание нового объекта; (C2) инициализация его полей значениями по умолчанию; (C3) присоединение к x. Для развернутого x, шаг C1 неуместен, а шаг C3 бесполезен; так что единственный эффект состоит в инициализации полей значениями по умолчанию.
В общем случае, в случае присутствия развернутых типов инициализация по умолчанию предполагает выполнение шага C2. Предположим, что класс, развернутый или нет, включает развернутые атрибуты:
class F feature
u: BOOLEAN
v: INTEGER
w: REAL
x: C
y: expanded C
z: E
...
end
Класс E развернут, а класс C нет. Инициализация прямого экземпляра F включает установку поля u в false, v - в 0, w - в 0.0, x - ссылкой void, а экземпляры y и z станут экземплярами классов C и E соответственно, чьи поля будут инициализированы в соответствии со стандартными правилами. Этот процесс инициализации может быть рекурсивно продолжен, поскольку поля экземпляров C и E могут быть в свою очередь развернутыми.
Как можно было понять, использование развернутых типов требует введения некоторых ограничений, гарантирующих, что рекурсивный процесс создания будет конечным. Хотя, как отмечалось ранее, клиентские отношения в общем случае могут включать циклы, такие циклы не должны включать развернутые атрибуты. Например, недопустимо для класса C иметь атрибут типа expanded D, если класс D имеет атрибут типа expanded C. Это означало бы, что каждый объект Cвключал бы подобъект D, который бы включал подобъект C и так далее. Сформулируем правило "развернутого клиента", ранее введенное неформально:
Правило Развернутого Клиента
Пусть отношение "развернутый клиент" определяется следующим образом: класс C является развернутым клиентом класса S, если некоторый атрибут C является развернутым типом, основанным на классе S.
Тогда отношение развернутый клиент не может включать никаких циклов.
Другими словами, не может существовать множества классов A, B, C, ... N, где каждый последующий является развернутым клиентом предыдущего, а последний класс N является развернутым клиентом класса A. В частности, класс A не может иметь атрибут типа expanded A, так как это делало бы класс A своим развернутым клиентом.
Недопустимость ссылок на подобъекты
Заключительное замечание ответит на вопрос, как сочетаются ссылки и подобъекты. Развернутый класс или развернутый тип, основанный на ссылочном классе, может иметь ссылочные атрибуты. Вполне допустимо, чтобы подобъект содержал ссылки на объекты, как показано на рисунке:
Рис. 8.21. Подобъект со ссылкой на другой объект
Приведенная ситуация предполагает следующие объявления:
Class COMPOSITE1 feature
other: SOME_TYPE
sub: expanded C
end
class C feature
ref: D
x: OTHER_TYPE; y: YET_ANOTHER_TYPE
end
class D feature
...
end
Каждый экземпляр класса COMPOSITE, такой как O_COMP на рис.8.21, имеет подобъект, (OC на рисунке) содержащий ссылку ref, которая может быть присоединена к объекту (OD на рисунке).
Но противоположная ситуация, где ссылка становится присоединенной к объекту, невозможна. Это будет следовать из правил присваивания и передаче аргументов, изучаемых в следующем разделе. Итак, структура времени выполнения никогда не может находиться в ситуации, показанной на рис.8.22, где OE содержит ссылку на OC, - подобъект O_CMP1, и OC содержит ссылку на себя.
Рис. 8.22. Ссылка на подобъект
Это правило открыто для критики, поскольку оно ограничивает моделирующие возможности подхода. Предыдущая версия нотации книги допускала ссылки на подобъекты. Но эта возможность порождала больше проблем, чем она того стоит:
[x]. С позиций реализации: механизм сборки мусора в этом случае должен быть готов справляться со ссылками на подобъекты, даже если в текущем выполнении будет всего несколько подобных ссылок или их вообще не будет. Это приводит к существенной потере производительности.
[x]. С позиций моделирования: ссылки на подобъекты заставляют отказаться от упрощения описания системы, что можно сделать, определив единственную ссылочную единицу - объект.
- Лекция 8. Динамические структуры: объекты
- ЧАСТЬ IV. База данных и ее объекты.
- Типы данных для работы с датой и временем
- Большие целые типы
- Физические объекты
- Типы страниц и их использование
- 1.2.3. Константы, переменные и типы
- 4. Лекция: Типы данных
- 9.1. Классы и прототипы
- 4. Кортежи. Типы кортежей
- 5. Отношения. Типы отношений
- 1. Базовые типы данных