Реферат: Программирование ориентированное на объекты
Еще 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ных типов). В этом смысле две разновидности динамической памяти делят этот класс памяти на два подкласса: память для интерпретации пассивных объектов (собственно динамическая) и память для интерпретации активных объектов (автоматическая). Несмотря на общность класса (динамическая память), распределение памяти в этих подклассах основано на разных принципах и реализуется совершенно разными алгоритмами.
Управление динамической памятью для пасссивных объектов (в дальнейшем просто динамической памятью) реализуется на основе двух основных процедур (обычно импортируемых из системного модуля):
PROCEDURE ALLOCATE (VAR A: ADDRESS; N: CARDINAL);
PROCEDURE DEALLOCATE (VAR A: ADDRESS; N: CARDINAL); .
Здесь А - свободный указатель, который укажет на выделенную область памяти (элемент хранения размером N байт) при вызове ALLOCATE и получит значение NIL (т.е. никуда не будет указывать) при освобождении этой области "из-под" А путем вызова DEALLOCATE.
Использование ограниченных указателей делает во многих отношениях целесообразным использование специальных вызовов: NEW(P) и DISPOSE(P), где VAR P: POINTER TO <Объект>. (NEW и DISPOSE - псевдопроцедуры, их вызовы транслируются в вызовы ALLOCATE и DEALLOCATE соответственно). Использование NEW и DISPOSE позволяет избежать многих семантических ошибок, связанных с различными значениями N в последовательности вызовов ALLOCATE...DEALLOCATE, определяющей создание/уничтожение одного и того же объекта.
В целом последовательность вызовов NEW...DISPOSE (или соответственно ALLOCATE...DEALLOCATE), в общем случае полностью определяемая логикой программиста, порождает ряд проблем, связанных с организацией и распределением свободного пространства динамической памяти. Одной из таких проблем является проблема фрагментации. Эффект фрагментации заключается в том, что рабочая область динамической памяти "дробится" на части - фрагменты различной длины. Какие-то из них "заняты" - используются программистом под элементы хранения его объектов, какие-то "свободны", причем характер чередования свободных и занятых фрагментов в общем случае может быть совершенно произвольным. Любой запрос программиста на создание нового объекта приводит к тому, что система управления динамической памятью "подбирает" ему фрагмент, подходящий по размерам. Правила такого подбора могут быть различны, но общая закономерность одна: такой фрагмент должен иметь размер, не меньший, чем запрашиваемый программистом. Если подходящий фрагмент имеет больший размер, чем требуется, в прикладную программу будет отдана его часть, котоpая тепеpь будет pассматpиваться системой как занятый фpагмент, а остаток останется в свободной зоне в качестве свободного фpагмента. При этом проблема фрагментации заключается в том, что эффект "дробления" может привести к тому, что в свободной зоне будет находиться множество "маленьких" разрозненных свободных фрагментов, в совокупности составляющих достаточный объем. Тем не менее, несмотря на такой объем, запрос программиста на новый элемент памяти может получить отказ по причине отсутствия целого подходящего элемента. Ниже приведен фрагмент программы и схема распределения динамической памяти, иллюстрирующие эффект фрагментации. При этом для простоты предполагается, что общий объем динамической памяти составляет 20 байт.
TYPE Треугольник = POINTER TO Фигура_1
Фигура_1 = RECORD
Сторона_1, Сторона_2, Сторона_3:CARDINAL
END;
Четырехугольник = POINTER TO Фигура_2;
Фигура_2 = RECORD
Сторона_1, Сторона_2, Сторона_3, Сторона_4:
CARDINAL
ЕND
VAR T1, T2: Треугольник; М1, М2: Четырехугольник;
BEGIN NEW(T1);... NEW(M1);... NEW(T2);...
DISPOSE(T1);... DISPOSE(T2); NEW(M2);...
┌───────────────────┐ ─┐
│ WORD │ │
├───────────────────┤ │
│ │ > Свободный фрагмент, ранее
├───────────────────┤ │ использованный под
│ │ │ объект Т1^
├───────────────────┤ ─┘─┐
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │
├───────────────────┤ │
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │
├───────────────────┤ > Фрагмент, занятый
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │ под объект М1^
├───────────────────┤ │
│▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒│ │