Ял <* Лодка <* Транспортное_Средство.
Такая декларация определяет три возможные интерпретации объекта на разных уровнях обобщения (pасшиpения свойств).
Еще pаз подчеpкнем, что между двумя рассмотренными видами полиморфной интерпретации объектов (записи с вариантами и наследование свойств) существует принципиальное различие: записи с вариантами реализуют полиморфную интерпретацию на альтернативной основе, а механизм наследованиния - на основе расширения свойств классов.
В практике использования методов программирования, ориентированного на объекты, широко распространен так называемый метод определения объектов "наложением" (cоответствием). Этот метод может быть реализован разными способами, мы его рассмотрим на примерах, используя концепцию типа как "трафарета" (маски), определяющего вид интерпретации объекта "под маской". Конструируя средствами языка различные "маски", программист получает возможности полиморфной интерпретации объекта.
Пример1.
TYPE POINT = RECORD X,Y: INTEGER END;
Point = RECORD Y,X: INTEGER END;
VAR A: ARRAY[1..2] OF WORD;
P: POINTER TO POINT;
p: POINTER TO Point;
X,Y: INTEGER;
BEGIN X:=1; Y:=5;
P:=ADR(A); (*1*)
P^.X:=X; P^.Y:=Y; (*2*)
p:=ADDRESS(P); (*3*)
X:=p^.X; Y:=p^.Y (*4*)
Этот пример реализует "трансформацию" объекта-точки с декартовыми координататами (1,5) в объект-точку с координатами (5,1). В программе задан элемент хранения А размером в два слова, "маска" POINT, "привязанная" к указателю Р, и "маска" Point, связанная с ограниченным указателем р. Операция (1) связана с "наложением" маски POINT на элемент хранения А и записью "через трафарет" значений координат точки в область памяти А. Операция (3) связана с наложением на ту же область памяти маски (трафарета) Point и чтением координат точки через новый трафарет. Таким образом, один и тот же объект, размещенный в А, интерпретируется в этом примере двояко: как точка с координатами (1,5) и симметричная ей точка с координатами (5,1). Заметим, что реально никакого преобразования координат не происходит, - все определяетсся структурой трафарета - маски, через которуюю мы смотрим на объект. (Расссматривая этот пример, ответьте на вопрос, почему для записи операторов (2) и (4) не используется присоединение?)
Поскольку множественность интерпретаций объекта определяется множеством масок, которые могут накладываться на одну и ту же область памяти, использование метода наложения связано с контролем размеров таких масок, соответствия их размерам элементов хранения и т.д. "Выход" маски за пределы элемента хранения интерпретируемого объекта чреват непредсказуемыми ошибками (работа с "чужой" областью памяти). Наложение нескольких масок на один и тот же объект желательно выполнять по адресу элемента хранения объекта без дополнительных смещений "внутрь" структуры объекта. Если несколько разных масок частично совместны (имеют части с идентичными атрибутами, одинаково интерпретируемые части), желательно общие идентичные части располагать в начале маски (вверху), а не в середине или в конце (внизу). Эти простые рекомендации помогают избежать многих ошибок, связанных с полиморфной интерпретацией объекта. (Заметим, что такие ошибки имеют свойства скрытого "проявления", очень трудно обнаруживаются и идентифицируются).
Во многих прикладных задачах метод наложения связан с использованием масок, определяемых структурами различных массивов. Например, задан массив кардинальных чисел и требуется его "трансформировать" в массив символов. Наложение в этом случае является наиболее "естественным" методом такой трансформации:
VAR C: ARRAY [1..100] OF CARDINAL;
P: POINTER TO ARRAY [1..200] OF CHAR;
CH: ARRAY [1..200] OF CHAR;
BEGIN
P := ADR(C); FOR I:=1 TO 200 DO CH[I]:=P^[I] END;...
Такие задачи связаны, как правило, с перекодировкой, преобразованием, трансформацией и т.п. больших массивов. Успех использования метода наложения здесь полностью определяется тем, удастся ли подобрать адекватную структуру маски-трафарета. Если удастся, то подобные преобразования могут быть выполнены очень просто, без использования специальных вычислений, связанных с различными форматами хранения данных, и неизменно сопутствующей им адресной арифметики. Попутно заметим, что использование метода наложения может помочь "обойти" многие ограничения, связанные с языком программирования. Например, используя наложение при интерпретации объектов, размещаемых в классе динамической памяти, можно "обойти" ограничения, связанные со статическими (константно - определяемыми) размерами массивов.
В заключение этой главы остановимся на самоинтерпретируемых объектах. Возможности самоинтерпретации связаны с использованием объектов процедурного типа, объектов-действий. Эта разновидность объектов сравнительно мало используется в технике "повседневного" программирования, в методологии же объектно-ориентированного подхода им отводится особая роль, роль активных объектов - акторов, определяющих динамику параллельно развивающихся процессов интерпретации.
Процедурный тип (или сигнатура, см. pазд. II) определяет множество возможных действий, видов активности. Например,
TYPE Действие = PROCEDURE (Станок);
определяет сигнатуру для класса Станок. Пусть множество действий над Станком ограничивается двумя:
PROCEDURE Включить (С: Станок);
PROCEDURE Выключить (С: Станок); .
Декларация VAR D: Действие определяет объект класса Действие. Такой объект может хранить потенциально возможное действие над Станком (т.е. "помнить", что нужно сделать) и (в подходящее время) активизироваться (самоинтерпретироваться) по отношению к станку:
VAR D: Действие; C: Станок;
BEGIN...
D:=Включить;...
D(C);... D:= Выключить;... D(C); .
Операторы D(C) в этом фрагменте определяют самоинтерпретацию объекта D в отношении объекта С, а операторы присваивания - определение объекта D потенциально возможным действием. Образно говоря, операторы присваивания здесь "взводят курок" D, а когда D "выстрелит" и какой будет эффект от этого "выстрела" (включает он станок С или выключает) определяется в общем случае логикой программы. Использование в программе переменных класса Действие аналогично наличию множества взведенных курков, при этом отдельные "выстрелы" превращаются в треск автоматных очередей - самоинтерпpетаций. Учитывая, что любое действие, связанное с такой самоинтерпретацией, может переопределить объекты-действия, логика выполнения подобных программ становится весьма запутанной. Основное применение этого механизма - моделирование сложных систем.
V. СОЗДАНИЕ / УНИЧТОЖЕНИЕ ОБЪЕКТОВ
"Время жизни" объекта. - Классы памяти. - Управление динамической памятью. - Фрагментация. - Проблемы "висячих" ссылок и мусора. - Автоматическая память. - Локальная среда. - Активации объекта.
Объекты, существующие в программе, делятся на две категории: статические и динамические. Эти категории определяются по-разному: на основе изменения состояния объектов модели и на основе "времени жизни" объектов. Первое определение предполагает, что любой объект, изменяющий свое состояние в процессе работы программы, является динамическим. В этом отношении, строго говоря, статическими объектами являются только константы, все объекты-переменные могут считаться динамическими. Второе определение предполагает возможность временного существования объектов, возможности создания и уничтожения объектов. В этом смысле объекты, время существования которых равно времени выполнения программы, расцениваются как постоянно существующие (статические), объекты же, время существования (жизни) которых меньше времени выполнения программы - как динамические. Второе определение касается объектов, которые идентифицируются только через указатели. Объекты, идентифицированные именем, в этом отношении всегда должны расцениваться как статические, поскольку их "создание" подготавливается транслятором и ассоциация между именем и элементом хранения объекта существует до окончания вpемени pаботы программы.
Создание объекта следует интерпретировать как выделение памяти под его элемент хранения. Такая интерпретация подразумевает разделение всего рабочего пространства памяти ЭВМ на две категории, два класса - статическую память и динамическую. Первый класс памяти, как следует из этого контекста, полностью находится под упpавлением тpанслятоpа и pаспpеделяется под статические объекты, существующие в системе постоянно. Например, декларация
VAR A: POINTER TO CARDINAL;
B: CARDINAL;
сообщает транслятору о необходимости "зарезервировать" в классе статической памяти два слова под элемент хранения объекта с именем А и одно слово под элемент хранения объекта с именем В.
Динамическая память предназначается для создания временно существующих объектов. Этот класс памяти имеет две разновидности: собственно динамическую и автоматическую. Собственно динамическая память (в отличие от статической) полностью находится в распоряжении программиста: по его директивам происходит выделение элементов хранения (создание объектов) и возврат ранее выделенных элементов в "зону" свободной памяти (пул "свободных" элементов), что в этом смысле равносильно "уничтожению" объекта.
Автоматическая память - особая разновидность динамической, которая также управляется директивами программиста, связанными с интерпретацией активных объектов (переменных пpоцедуpных типов). В этом смысле две разновидности динамической памяти делят этот класс памяти на два подкласса: память для интерпретации пассивных объектов (собственно динамическая) и память для интерпретации активных объектов (автоматическая). Несмотря на общность класса (динамическая память), распределение памяти в этих подклассах основано на разных принципах и реализуется совершенно разными алгоритмами.