Внутреннее представление объектов
Внутренний формат данных объекта имеет сходство с внутренним форматом записи. Поля объекта записываются в порядке их описаний как непрерывная последовательность переменных. Любое поле, унаследованное от родительского (порождающего) типа, записывается перед новыми полями, определенными в дочернем (порожденном) типе.
Если объектный тип определяет виртуальные методы, конструкторы или деструкторы, то компилятор размещает в нем дополнительное поле данных. Это 16–битовое поле, называемое полем таблицы виртуальных методов, используется для запоминания смещения таблицы виртуальных методов в сегменте данных (рис. 13). Поле таблицы виртуальных методов располагается непосредственно после обычных полей объектного типа. Если объектный тип наследует виртуальные методы, конструкторы или деструкторы, то он также наследует и поле ТВМ, благодаря чему дополнительное поле таблицы виртуальных методов не размещается.
Инициализация поля таблицы виртуальных методов экземпляра объекта осуществляется конструктором объектного типа. Программа никогда не инициализирует поле таблицы виртуальных методов явно и не имеет к нему доступа.
Модули, экспортирующие объекты
Для определения объектов часто используются модули. Типы объектов можно описывать в интерфейсном разделе модуля, а тела процедур и функций, реализующих методы объектов, определяются в разделе реализации модуля. Если необходимо экспортировать переменные типа “объект”, содержащие виртуальные методы, то для таких переменных в разделе инициализации можно разместить вызовы конструкторов. Модули могут иметь свои собственные определения типов объектов в разделе реализации. Tакие типы подчиняются тем же ограничениям, что и любые другие типы, определенные в разделе реализаций модуля. Тип объекта, определенный в интерфейсном разделе модуля, может иметь производные типы в разделе реализации модуля. В случае, когда модуль В использует модуль А, модуль В может определять производные типы от любого типа объекта, экспортируемого модулем А.
Модули, содержащие программные инструментальные средства (например, программы работы с меню, с окнами, с графикой и т.д.), могут поставляться конечным пользователям в виде подключаемых TPU-файлов с распечаткой типов объектов и методов, определенных в интерфейсном разделе модуля. Пользователи такого модуля могут, используя полиморфизм и наследование, добавлять к модулю новые свойства. Таким образом, программа наследует все, что было в исходном модуле, и создает на основе этого новые объекты.
Учебная задача
Пусть требуется разработать программу, которая создает на экране ряд графических изображений (точки, окружность, линия, квадрат) и может перемещать эти изображения по экрану. Пример заимствован из [6].
Создадим объект–родитель TGraphObject, в рамках которого будут инкапсулированы поля и методы, общие для всех остальных объектов:
Type
TGraphObj = Object
x, y : Integer; color : Word;
constructor init(ax, ay : Integer; acolor : Word);
procedure Draw(acolor : Word); Virtual;
procedure Show;
procedure Hide;
procedure Moveto(dx, dy : Integer);
end;
В объекте определены следующие поля: x, y – координаты реперной точки; Color – цвет фигуры. Реперная точка характеризует текущее положение графической фигуры на экране. В дальнейшем предполагается создать объекты – потомки от TGraphObj, реализующие все специфические свойства точки, линии, окружности и прямоугольника. Каждый из этих графических объектов будет характеризоваться положением на экране (поля x и y) и цветом (поле Color). С помощью метода Draw он будет способен отображать себя на экране, а с помощью методов Show и Hide – показать и спрятать себя. Метод MoveTo позволит объекту перемещаться по экрану. Учитывая общность свойств графических объектов, объявляется абстрактный объект TGraphObj, который не связан с конкретной графической фигурой. Он объединяет в себе все общие поля и методы реальных фигур и будет служить родителем для других объектов.
Разные экземпляры одного и того же объекта отличаются друг от друга только содержимым объектных полей, в то время как каждый из них использует одни и те же объектные методы. Конструктор Init объекта TGraphObj получает все необходимые данные для полного определения экземпляра.
Процедура Draw предназначена для вычерчивания графического объекта. Эта процедура будет реализовываться в потомках объекта TGraphObj по‑разному. Для визуализации точки следует вызвать процедуру PutPixel, для вычерчивания линии – процедуру Line и т.д. В объекте TGraphObj процедура Draw определена как виртуальная (“воображаемая”). Абстрактный объект TGraphObj не предназначен для вывода на экран, однако, ее наличие в этом объекте говорит о том, что любой потомок TGraphObj должен иметь собственный метод Draw, с помощью которого он может показать себя на экране.
ТВМ объекта TGraphObj хранит единственный элемент – адрес метода Draw. При создании экземпляра объекта с помощью вызова конструктора Init в ТВМ помещается нужный адрес метода Draw.
Наличие в объекте TGraphObj виртуального метода Draw позволяет легко реализовать три других метода объекта. Чтобы показать объект на экране в методе Show, вызывается Draw с цветом aColor, равным значению поля Color, а чтобы спрятать графический объект, в методе Hide вызывается Draw c значением цвета GetBkColor, т.е. с текущим цветом фона.
Если потомок TGraphObj хочет переместить себя на экране, он обращается к родительскому методу MoveTo. В этом методе сначала с помощью Hide oбъект сначала стирается с экрана, а затем с помощью Show показывается в другом месте. Для реализации своих действий и Show, и Hide обращаются к виртуальному методу Draw. Поскольку вызов MoveTo происходит в рамках определенного объекта, используется ТВМ этого объекта и вызывается нужный метод Draw.
Чтобы описать все свойства объекта, необходимо раскрыть содержимое объектных методов, т.е. описать соответствующие процедуры и функции. Описание методов производится обычным для Паскаля способом в любом месте раздела описаний, но после описания объекта.
Описание конструктора:
Constructor TGraphobj.init;
Begin x := ax; y := ay; color := acolor; end;
При описании методов имя метода дополняется спереди именем объекта, т.е. используется составное имя метода. Составные имена четко указывают принадлежность конкретной процедуры.
Абстрактный объект TGraphObj не предназначен для вывода на экран, поэтому его метод Draw ничего не делает.
Procedure TGraphObj.draw;
Begin End;
Методы Hide, Show и MoveTo “знают” формат вызова этого метода и реализуют необходимые действия, обращаясь к реальным методам Draw своих будущих потомков через соответствующие ТВМ.
Procedure TGraphObj.Show;
Begin Draw(color); End;
Procedure TGraphObj.Hide;
Begin Draw(getbkcolor); End;
Procedure TGraphObj.MoveTo;
Begin Hide; x := x + dx; y := y + dy; Show; End;
Создадим простейший потомок от TGraphObj – объект TPoint, с помощью которого будет визуализироваться и перемещаться точка. Все основные действия, необходимые для этого, уже есть в объекте TGraphObj, поэтому в объекте TРoint перекрывается единственный метод – Draw:
Type
TPoint = Object(TGraphObj)
Procedure Draw(acolor : word); Virtual;
End;
Procedure TPoint.Draw;
Begin PutPixel(x, y, color); End;
В новом объекте TPoint можно использовать любые методы объекта-родителя TGraphObj. Например, вызвать метод MoveTo, чтобы переместить изображение точки на новое место. В этом случае родительский метод TGraphObj.MoveTo будет обращаться к методу TPoint.Draw, чтобы спрятать и затем показать изображение точки. Такой вызов станет доступен после обращения к конструктору Init объекта TPoint, который нужным образом настроит ТВМ объекта. Если вызвать TPoint.Draw до вызова Init, его ТВМ не будет содержать правильного адреса и программа “зависнет”.
Чтобы создать объект-линию, необходимо ввести два новых поля для хранения координат второго конца. Дополнительные поля требуется наполнить конкретными значениями, поэтому нужно перекрыть конструктор родительского объекта:
TLine = Object(TGraphObj)
dx, dy : integer;
Constructor init(x1, x2, y1, y2 : integer; acolor : word);
Procedure Draw(acolor : word); Virtual;
End;
где dx, dy – приращения координат второго конца. Конструктор TLine.Init вызывает унаследованный конструктор TGraphObj для инициализации полей x, y и color. Затем инициирует поля dx, dy.
Constructor TLine.init;
Begin
Inherited init(x1, y1, acolor);
dx := x2 - x1; dy := y2 - y1;
End;
В конструкторе TLine.init для инициализации полей x, y, color, унаследованных от родительского объекта, вызывается унаследованный конструктор TGraphObj.Init.
Для инициализации полей dx, dy вычисляется расстояние в пикселях по горизонтали и вертикали от первого конца прямой до ее второго конца. Это позволяет в методе TLine.Draw вычислить координаты второго конца по координатам первого и смещениям dx и dy.
Procedure TLine.Draw;
Begin
SetColor(acolor); Line(x, y, x + dx, y + dy);
End;
Аналогично реализуется объект TCircle для создания и перемещения окружности:
Type
TCircle = Object (TGraphObj)
r : Integer;
Constructor Init (ax, ay, ar : Integer; acolor : Word);
Procedure Draw (acolor : Word); Virtual;
End;
Constructor TCircle.Init;
Begin
Inherited Init(ax, ay, acolor);
r := ar
End;
Procedure TCircle.Draw;
Begin
SetColor(acolor);
Circle (x, y, r)
End;
Программа, реализующая перемещения точки и линии, приводится полностью:
uses graph;
Type TGraphobj = Object
x, y : integer; color : word;
Constructor init(ax, ay : integer; acolor : word);
Procedure Draw(acolor : word); Virtual;
Procedure Show;
Procedure Hide;
Procedure Moveto(dx, dy : integer);
End;
TPoint = Object(TGraphObj)
Procedure Draw(acolor : word); Virtual;
End;
TLine = Object(TGraphObj)
dx, dy : integer;
Constructor init(x1, x2, y1, y2 : integer; acolor : Word);
Procedure Draw(acolor : word);Virtual;
End;
Constructor TGraphobj.init;
Begin x := ax; y := ay;color := acolor; End;
Procedure TGraphObj.Draw;
Begin End;
Procedure TGraphObj.Show;
Begin Draw(color); End;
Procedure TGraphObj.Hide;
Begin Draw(getbkcolor); End;
Procedure TGraphObj.MoveTo;
Begin Hide; x := x + dx; y := y + dy; Show; End;
Procedure TPoint.Draw;
Begin PutPixel(x, y, color); End;
Constructor TLine.init;
Begin
Inherited init(x1,y1,acolor);
dx := x2 - x1; dy := y2 - y1;
End;
Procedure TLine.Draw;
Begin
SetColor(acolor); line(x,y,x+dx,y+dy);
End;
Var d, m : integer; pp : TPoint; ll : TLine;
{рр – экземпляр объекта TPoint; ll – экземпляр объекта TLine}
Begin
d := detect;
Initgraph(d, m, ‘c:\pascal\bgi’); {Инициализация графики}
if graphresult <> 0 then halt(1);
{Создали и передвинули линию}
ll.init(10, 20, 10, 20, 3); ll.Мoveto (50, 50);
{Создали и передвинули точку}
pp.init(10, 10, 4); pp.МoveTo(50, 50);
readln;
end.
Идею инкапсуляции полей и методов можно применить не только к графическим объектам, но и ко всей программе в целом. Можно создать объект–программу, реализующую три основные действия: инициализацию, выполнение основной работы, завершение работы. На этапе инициализации экран переводится в графический режим работы и создаются графические объекты. На этапе выполнения осуществляется сканирование клавиатуры и перемещение графических объектов. На этапе завершения экран переводится в текстовый режим и завершается работа всей программы.
Программу можно разместить в модуле.
Unit GraphApp;
Interface
Type
TGraphApp = Object
Procedure Init;
Procedure Run;
Destructor Done;
End;
Implementatiоn
Procedure TGraphApp.Init;
. . .
End;
. . .
End.
В этом случае основная программа будет выглядеть:
Program Grаph_Objects;
Uses GraphApp;
Var App : TGraphApp;
Begin
App.Init;
App.Run;
App.Done
End.
В программе создан экземпляр App объекта–программы TGraphApp. Исполняемая часть программы состоит из обращения к трем методам объекта.
При использовании динамических переменных программа может быть следующей:
Program Grаph_Objects;
Uses GraphApp;
Type
PGraphApp = ^ TGraphApp;
Var App : PGraphApp;
Begin
App := New(PGraphApp, Init);
App^.Run;
App^.Done
End.
Если описания графических объектов разместить в отдельном модуле GraphObj, то возможен следующий вариант модуля учебной программы:
Unit GraphApp;
Interface
Uses GraphObj; {Подключаем модуль графических объектов}
Const NPoints = 100;
Type
{Объект–программа}
TGraphApp = Object
Points : Array [1 .. Npoints] of Tpoint;
Line : Tline;
Rect : TRect;
Circ : TCircle;
ActiveObj : Integer;
Procedure Init;
Procedure Run;
Procedure Done;
Procedure ShowAll;
Procedure MoveActiveObj (dx, dy : Integer);
End;
Implementation
Uses Graph, Crt;
Procedure TGraphApp.Init;
{Инициирует графический режим работы экрана. Создает и отображает Npoints экземпляров объекта Tpoint, а также экземпляры объектов Tline, Tcircle и Trect}
Var d, r, Err, k : Integer;
Begin
{Инициируем графику}
d := Detect;
InitGraph (d, r, ‘\bgi’);
Err := GraphResult;
If Err<>0 Then
Begin
writeln(‘Ошибка ‘,Err); Halt;
End;
{Создаем точки}
For k := 1 to Npoints do
Points[k].Init(Random(GetMaxX),Random(GetMaxY), Random(15) + 1);
{Создаем другие объекты}
Line.Init(GetMaxX div 3, GetMaxY div 3, 2 * GetMaxX div 3,
2 * GetMaxY div 3, LighRed);
Circ.Init(GetMaxX div 2, GetMaxY div 2, GetMaxY div 5, White);
Rect.Init(2 * GetMaxX div 5, 2 * GetMaxY div 5, 3 * GetMaxX div 5,
3 * GetMaxY div 5, Yellow);
ShowAll; {Показываем все графические объекты}
ActiveObj := 1 {Первым перемещаем прямоугольник}
End; {TGraphApp.Init}
Procedure TGraphApp.Run;
{Выбирает объект с помощью Tab и перемещает его по экрану}
Var
Stop : Boolean; {Признак нажатия Esc}
Const d = 5; {Шаг смещения фигур}
Begin
Stop := False;
{Цикл опроса клавиатуры}
Repeat
Case ReadKey Of
#27 : Stop := True; {Нажата Esc}
#9 : Begin {Нажата Tab}
inc(ActiveObj);
If ActiveObj > 3 Then ActiveObj := 3
End;
#0 : case Readkey of
#71 : MoveActiveObj(-d, -d); {Влево и вверх}
#72 : MoveActiveObj(0, -d); {Вверх}
#73 : MoveActiveObj(d, -d); {Вправо и вверх}
#75 : MoveActiveObj(-d, 0); {Влево}
#77 : MoveActiveObj(d, 0); {Вправо}
#79 : MoveActiveObj(-d, d); {Влево и вниз}
#80 : MoveActiveObj(0, d); {Вниз}
#81 : MoveActiveObj(d, d); {Вправо и вниз}
End
End;
ShowAll;
Until Stop
End; {TGraphApp.Run}
Destructor TGraphApp.Done;
{Закрывает графический режим}
Begin
CloseGraph
End; {TGraphApp.Done}
Procedure TgraphApp.ShowAll;
{Показывает все графические объекты}
Var
k : Integer;
Begin
For k := 1 to Npoints do Points[k].Show;
Line.Show;
Rect.Show;
Circ.Show;
End;
Procedure TGraphApp.MoveActiveObj;
{Перемещает активный графический объект}
Begin
Case ActiveObj of
1 : Rect.MoveTo (dx, dy);
2 : Circ.MoveTo (dx, dy);
3 : Line.MoveTo (dx, dy);
End;
End;
End.