Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
8.DOC
Скачиваний:
10
Добавлен:
12.05.2015
Размер:
321.54 Кб
Скачать

Interface

uses Graph, Crt;

type

Location = object

X, Y : Integer;

procedure Init(InitX, InitY : Integer);

function GetX : Integer;

function GetY : Integer;

end;

PointPtr = ^Point;

Point = object(Location)

Visible : Boolean;

constructor Init(InitX, InitY : Integer);

destructor Done; virtual;

procedure Show; virtual;

procedure Hide; virtual;

function IsVisible : Boolean;

procedure MoveTo(NewX, NewY : Integer);

procedure Drag(DragBy : Integer); virtual;

end;

CirclePtr = ^Circle;

Circle = object(Point)

Radius : Integer;

constructor Init(InitX, InitY : Integer; InitRadius : Integer);

procedure Show; virtual;

procedure Hide; virtual;

procedure Expand(ExpandBy : Integer); virtual;

procedure Contract(ContractBy : Integer); virtual;

end;

Implementation

{Реализация методов объекта Location}

procedure Location.Init(InitX, InitY : Integer);

begin

X :=InitX;

Y :=InitY;

end;

function Location.GetX : Integer;

begin

GetX := X;

end;

function Location.GetY : Integer;

begin

GetY := Y;

end;

{Реализация методов объекта Points}

constructor Point.Init(InitX, InitY : Integer);

begin

Location.Init(InitX, InitY);

Visible := False;

end;

destructor Point.Done; {назначение деструкторов поясняется позднее}

begin

Hide;

end;

procedure Point.Show;

begin

Visible :=True;

PutPixel(X, Y, GetColor);

end;

procedure Point.Hide;

begin

Visible :=False;

PutPixel(X, Y, GetBkColor);

end;

function Point.IsVisible : Boolean;

begin

IsVisible := Visible;

end;

procedure Point.MoveTo(NewX, NewY : Integer);

begin

Hide;

X :=NewX;

Y :=NewY;

Show;

end;

function GetDelta(var DeltaX,DeltaY : Integer) : Integer;

var

KeyChar : Char;

Quit : Boolean;

begin

DeltaX :=0;

DeltaY :=0; {0 – нет изменений в положении}

GetDelta :=True; {True – возвращает дельту}

repeat KeyChar := ReadKey; {cчитывание нажатой клавиши}

Quit :=True; {используемая клавиша}

case Ord(KeyChar) of

0: begin {“0” означает расширенный 2-байтовый код}

KeyChar := ReadKey; {читать второй байт кода}

case Ord(KeyChar) of

72: DeltaY :=-1; {стрелка вверх; декремент Y}

80: DeltaY :=1; {стрелка вниз; инкремент Y}

75: DeltaX :=-1; {стрелка влево; декремент X}

77: DeltaX :=1; {стрелка вправо; инкремент X}

else Quit :=False; {игнорирует любой другой код}

end; {case}

end;

13: GetDelta :=False; {нажатие CR означает “не дельта”}

else Quit :=False; {игнорирует все другие нажатия клавиш}

end; {case}

until Quit;

end;

procedure Point.Drag(DragBy : Integer);

var

DeltaX, DeltaY : Integer;

FigureX, FigureY : Integer;

begin

Show; {показ фигуры, которая будет буксироваться}

FigureX := GetX; {возврат начального положения фигуры}

FigureY := GetY;

while GetDelta(DeltaX, DeltaY) do {цикл буксировки}

begin { Применяет дельту к фигуре X,Y: }

FigureX :=FigureX + (DeltaX * DragBy);

FigureY :=FigureY + (DeltaY * DragBy);

MoveTo(FigureX, FigureY); {перемещение фигуры }

end;

end;

{ Реализация методов бъекта Circle}

constructor Circle.Init(InitX, InitY : Integer; InitRadius : Integer);

begin

Point.Init(InitX, InitY);

Radius :=InitRadius;

end;

procedure Circle.Show;

begin

Visible :=True;

Graph.Circle(X, Y, Radius);

end;

procedure Circle.Hide;

var

TempColor : Word;

begin

TempColor :=Graph.GetColor;

Graph.SetColor(GetBkColor);

Visible := False;

Graph.Circle(X, Y, Radius);

Graph.SetColor(TempColor);

end;

procedure Circle.Expand(ExpandBy : Integer);

begin

Hide;

Radius :=Radius + ExpandBy;

if Radius <0 then Radius :=0;

Show;

end;

procedure Circle.Contract(ContractBy : Integer);

begin

Expand(-ContractBy);

end;

{раздел инициализации}

begin

end.

В модули, которые написаны с применением обычной процедурной технологии, нельзя вносить изменения, если не принимать во внимание возможности изменений в его исходном тексте с последующей перекомпиляцией. Относительно таких модулей как, например, Figures, важным представляется то, что их функциональные свойства могут дополняться без изменения исходного текста. Для этого пользователю достаточно иметь информацию о интерфейсном разделе (например, его распечатку) и смысловую спецификацию полей и методов описанных в модуле объектов. Используя полиморфные объекты и виртуальные правила, пользователь имеет возможность расширять модуль с учетом требуемых ему собственных функциональных свойств.

Это новое понятие, заключающееся в пополнении функций без изменений исходного кода, носит название расширяемости или открытости.

Расширяемость, собственно, обусловлена наследованием и полиморфизмом (позднее связывание позволяет “учитывать дополнения” во время выполнения программы, так что расширение существующего модуля никак не связано с его изменением).

Следующая программа использует модуль Figures, и расширяет его, дополняя новым объектом графической фигуры Arc (дуга). Arc является производным типом от объекта Circle и наследует методы MoveTo или Drag без каких-либо особых изменений. Позднее связывание позволяют методу Drag вызывать виртуальные методы Show и Hide объекта Arc вне зависимости от того, когда был откомпилирован Point.Drag:

program FigureDemo; {расширение Figures типом Arc}

uses Crt, DOS, Graph, Figures;

type

Arc = object(Circle)

StartAngle, EndAngle : Integer;

constructor Init (InitX, InitY: Integer; InitRadius: Integer;

InitStartAngle, InitEndAngle : Integer);

procedure Show; virtual;

procedure Hide; virtual;

end;

var

GraphDriver : Integer;

GraphMode : Integer;

ErrorCode : Integer;

AnArc : Arc;

ACircle : Circle;

{Определение методов объекта Arc}

constructor Arc.Init(InitX, InitY : Integer; InitRadius : Integer;

InitStartAngle, InitEndAnle : Integer);

begin

Circle.Init(InitX, InitY, InitRadius);

StartAngle :=InitStartAngle;

EndAngle :=InitEndAngle;

end;

procedure Arc.Show;

begin

Visible :=True;

Graph.Arc(X, Y, StartAngle, EndAngle, Radius);

end;

procedure Arc.Hide;

var

TempColor : Word;

begin

TempColor :=Graph.GetColor;

Graph.SetColor(GetBkColor);

Visible :=False;

Graph.Arc(X, Y, StartAngle, EndAngle, Radius); {pисует дугу в цвете

заднего фона для ее скрытия}

SetColor(TempColor);

end;

{Тело программы}

begin

GraphDriver := Detect; {определение типа монитора}

InitGraph(GraphDriver, GraphMode,'');

if GraphResult <> GrOK then

begin

WriteLn('Halted on graphics error:');

Halt(1)

end;

{Все потомки от типа Point содержат виртуальные правила и должны

быть инициализированы вызовом конструктора}

ACircle.Init(151, 82, 50);

AnArc.Init(151, 82, 25, 0, 90);

{AnArc заменяется экземпляром AnCircle для буксировки круга вместо дуги.

Нажать ENTER для остановки буксировки и завершения программы}

AnArc.Drag(5);

CloseGraph;

end.

Свойство расширяемости является основой для создания мощных библиотек, которые сами могут рассматриваться как дополнение инструментария языка. Примерами таких библиотек являются Turbo Vision или, в последних версиях, Pascal Object Library. Однако, свойство расширяемости вступает в некоторые противоречия с эффективностью программ, написаных с применением ООП-технологии. Эти противоречия вызваны необходимостью выбора раннего или позднего связывания при описании методов. В общем, предпочтение следует отдавать виртуальным методам. Статические методы можно использовать в тех случаях, когда требуется достичь большей скорости и эффективности использования памяти. Справедливости ради, следует заметить, что выигрыш здесь будет небольшим (потери при позднем связывании определяются необходимостью “обработки” VMT), а дополнительная скорость и эффективность использования памяти, которую дают статические правила, компенсируются гибкостью полиморфных объектов.

  1. Динамические объекты

Все примеры объектов, рассматриваемые до сих пор, имели статические экземпляры (термин "статический"здесь имеет смысл в контексте понятия “переменная” и не имеет никакого отношения к статическим правилам).

Экземпляры объктного типа, подобно переменным, могут размещаться в динамической области (heap). В этом случае принято говорить о динамических объектах. Кроме того, объектный тип можно использовать в качестве компонент динамических (рекурсивных) структур данных, если среди полей данных предусмотрено поле, которое является ссылкой на этот же тип объекта.

Согласно общим правилам для динамически размещаемых переменных новый экземпляр объекта размещается в памяти с помощью процедуры New. При этом в динамической области (heap) выделяется место для экземпляра, и указателю-параметру New возвращается соответствующий этому месту адрес, например:

type

Location =object

X, Y : Integer;

procedure Init(InitX, InitY : Integer);

function GetX : Integer;

function GetY : Integer;

end;

PointPtr =^Point;

Point =object(Location);

. . .

var

PPoint : PointPtr;

begin

new (PPoint);

. . .

Если динамический объект содержит виртуальные методы, он затем должен быть обязательно инициализирован с помощью вызова конструктора до того, как делаются любые вызовы его методов, т. е. способ инициализации полиморфного объекта остается прежним.

Вызов методов объекта затем может быть выполнен обычным способом, посредством указателей или ссылок, которые заменяют имя экземпляра, например:

ХPosition :=PPoint ^.GetX;

Однако, синтаксис процедуры new применительно к динамическим объектам в Borland Pascal существенно расширен (в том числе и по отношению к расширениям, описанным в разделе 7), что позволяет выделять полиморфному объекту требуемое место в динамической области и инициализировать его с помощью одной операции. В расширенной трактовке процедура new может быть вызвана с двумя параметрами: именем указателя в качестве первого параметра и вызовом конструктора в качестве второго. Наример, распределение памяти для объекта Circle с последующей его инициализацией выполняется с помощью вызова:

new(PCircle,Init(600, 100, 30));

При этом важно помнить, что конструктор Init фактически выполняет только динамическое распределение памяти. Имя экземпляра не может предшествовать Init, так как во время вызова New инициализируемый с помощью Init экземпляр еще не существует. Компилятор идентифицирует правило Init посредством типа указателя, передаваемого в качестве первого параметра, а не его значения, определяя тем самым только объем требуемой памяти для размещения полиморфного объекта.

Более того, процедура new в расширенной трактовке может использоваться как функция, возвращающая значения указателя, т. е. возможны присваивания вида:

type

ArcPtr =^Arc;

var

PArc : ArcPtr;

begin

PArc :=New(ArcPtr);

или

PArc :=new(ArcPtr, Init(600, 100, 25, 0, 90));

Для очистки памяти, которая была распределена под “использованные” динамические объекты, традиционно применяется процедура Dispose. Обычно вызов процедуры Dispose осуществляется оператором вида Dispose(PMyObject), где PMyObject – указатель на использованный динамический объект.

Однако, подобный вызов применим только к объектам, методы которого предполагают раннее связывание (описаны как статические). Для полиморфных объектов освобождаемый объем памяти не является постоянной величиной и определяется с помощью одной из строк таблицы VMT (см. Рис.8.2.)

При необходимости очистить память, ранее распределенную под полиморфный объект, процедуре Dispose необходимо “сообщить”, сколько байт динамической области очистить. Во время компиляции информация о размере полиморфного объекта отсутствует. В таблице VMT для каждого типа объекта имеется размер требуемой для его размещения памяти и вариант этой таблицы становится доступным, благодаря параметру Self, значение которого устанавливается конструктором.

Более того, полиморфный объект может содержать указатели на динамические структуры или объекты, которые необходимо освободить или "стереть" в определенном порядке, особенно в случае, когда объект оперирует с другими сложными структурами данных. Все, что необходимо для стирания динамического объекта, должно быть собрано вместе, в одном методе, чтобы объект мог быть устранен одним его вызовом. Для таких методов обычно используется имя Done. Иными словами, метод Done должен инкапсулировать все детали стирания объекта и все структуры данных и объекты, вложенные в него. Допустимо и иногда удобно определять несколько методов, определяющих способы стирания данного типа объекта в зависимости от того, как они размещаются и используются, или же в зависимости от того, в каком состоянии находится объект, когда осуществляется его уничтожение из динамической памяти.

Подобно конструкторам в Borland Pascal для этого случая предусмотрен особый вид методов, которые называются деструкторами. Деструктор описывается в списке методов типа объекта с помощью зарезервированного слова destructor, например:

Point =object(Location)

Visible : Boolean;

constructor Init(InitX, InitY : Integer);

destructor Done; virtual;

. . .

end;

Для одного типа объекта могут быть определены несколько деструкторов. Деструкторы могут наследоваться и определяться либо статическими, либо виртуальными, причем последнее предпочтительней.

Зарезервированное слово destructor не является необходимым для каждого метода очистки, даже если определение типа объекта содержит виртуальные правила. Фактически деструкторы “работают” только с динамически распределяемыми объектами. Но описание деструкторов в списке методов для статически распределенных объектов вреда не принесет, а отсутствие деструкторов исключает возможность использовать такие объекты в качестве динамических.

Для очистки памяти, распределенной под “использованный” динамический объект, деструктор должен быть вызван как параметр процедуры Dispose, что соответствует ее расширенной трактовке применительно к динамическим объектам:

Dispose(PPoint,Done);

При этом деструктор определяет размер освобождаемой памяти в VMT данного типа экземпляра и передает этот размер процедуре Dispose. Процедура завершает очистку, освобождая соответствующую память. Таким образом, вызов деструктора вне процедуры Dispose не выполнит автоматической очистки памяти.

Число освобождаемых байт будет верным вне зависимости от того, указывает ли PPoint на экземпляр типа Point, или же на один из производных от Point типов, например, Circle или Arc.

Тело процедуры, описывающей деструктор, может быть пустым, т. е.:

destructor MyObject.Done;

begin

end;

и все-таки деструктор будет выполнять свою задачу. В таком деструкторе существенно не тело правила, а код, сгенерированный компилятором в ответ на служебное слово destructor.

Приведенный ниже пример иллюстрирует использование динамических объектов при формировании связанного списка графических объектов и последующей его очистки.

Для формирования рекурсивной структуры данных, компоненты которой представлены объектным типом, необходимо, чтобы список его полей включал поле ссылки. В Point такого поля нет. Простым решением было бы доопределить поле ссылки в описании типа Point, причем все производные типы унаследовали бы это поле. Доопределение Point возможно, если исходное описание этого объекта известно.

Решение, которое не требует внесения никаких изменений в описание графических объектов, состоит в создании нового типа объекта, не являющегося производным от объекта Point. Тип List (список) – простой объект, назначение которого состоит в определении указателя на голову списка объектов типа Point. В качестве компонент списка теперь можно использовать обычную запись, например, Node (узел), полями которой будут указатель на тип Point и ссылка Next:

. . .

NodePtr =^Node;

Node =record

Item : PointPtr;

Next : NodePtr

end;

Описание типа List имеет вид:

List = object

Nodes: NodePtr;

constructor Init;

destructor Done; virtual;

procedure Add(Item : PointPtr);

procedure Report;

end;

Применительно к объекту List интерес в данном примере представляет метод Add и деструктор Done.

Метод Add позволяет формировать список, помещая новую компоненту в его начало. В соответствии с определением совместимости объектных типов и указателей, указатель на любой тип фигуры, производный от Point, также может быть передан List.Add в параметре Item:

procedure List.Add(Item : PointPtr);

var

N : NodePtr;

begin

new(N);

N^.Item :=Item;

N^.Next :=Nodes;

Nodes :=N;

end;

В примере используется статически размещаемый экземпляр объекта типа ListAlist и формируется список с тремя узлами. Каждый узел указывает на отдельную графическую фигуру, которой является либо объект Point, либо один из его производных типов. Затем вся структура, включая три записи Node и три объекта типа Point, “очищается” и убирается из динамической области с помощью одного вызова деструктора AList.Done :

destructor List.Done;

var

N: NodePtr;

begin

while Nodes <> nil do

begin

N :=Nodes;

Dispose(N^.Item, Done);

Nodes :=N^.Next; Dispose(N)

end

end;

Таким образом список очищается начиная с "головы". Вызов dispose освобождает память, использованную для размещения первого элемента списка (Item^); затем Nodes передается следующей компоненте Node в списке оператором Nodes :=N^.Next; компонента снова освобождается и весь процесс повторяется, пока список не будет полностью очищен.

Здесь можно еще раз напомнить, что при вызове деструктора Dispose(N^.Item, Done) фактический тип N^.Item – не обязательно Point, Он также может быть любым, производным тип от типа Point. Удаляемый объект является полиморфным и размер его экземпляра определяется в соответствующей таблице виртуальных правил.

В тексте примера AList является экземпляром статического объекта. Однако, этот объект можно описать как динамический. В этом случае для его размещения в heap-области понадобиться указатель типа ListPtr, а для уничтожения – деструктор PList,Done, вызов которого Dispose(PList,Done) обеспечит очистку памяти от "головы" списка.

Полный текст примера с дополнительными комментариями приведен ниже; в нем используется уже известный модуль Figures, в котором тип Arc описан как производный тип от типа Point:

program prim8_1;

uses Graph, Figures;

type

ArcPtr = ^Arc;

Arc = object(Circle)

StartAngle, EndAngle : Integer;

constructor Init(InitX, InitY : Integer; InitRadius : Integer;

InitStartAngle, InitEndAngle : Integer);

procedure Show; virtual;

procedure Hide; virtual;

end;

NodePtr = ^Node;

Node = record

Item : PointPtr;

Next : NodePtr;

end;

ListPtr = ^List;

List = object

Nodes: NodePtr;

constructor Init;

destructor Done;virtual;

procedure Add(Item : PointPtr);

procedure Report;

end;

var

GraphDriver : Integer;

GraphMode : Integer;

Temp : string;

AList : List;

Parc : ArcPtr;

PCircle : CirclePtr;

RootNode : NodePtr;

{Процедуры, которые не являются правилами}

procedure OutTextLn(TheText : string);

begin

OutText(TheText);

MoveTo(0, GetY + 12)

end;

procedure HeapStatus(StatusMessage : string);

begin

Str(MemAvail : 6, Temp);

OutTextLn(StatusMessage + Temp);

end;

{Реализация методов объекта Arc }

constructor Arc.Init(InitX, InitY : Integer; InitRadius : Integer;

InitStartAngle, InitEndAngle : Integer);

begin

Circle.Init(InitX, InitY, InitRadius);

StartAngle := InitStartAngle;

EndAngle := InitEndAngle;

end;

procedure Arc.Show;

begin

Visible := True;

Graph.Arc(X, Y, StartAngle, EndAngle, Radius);

end;

procedure Arc.Hide;

var

TempColor : Word;

begin

TempColor := Graph.GetColor;

Graph.SetColor(GetBkColor);

Visible := False;

Graph.Arc(X, Y, StartAngle, EndAngle, Radius);

SetColor(TempColor);

end;

{Реализация методов объекта List }

constructor List.Init;

begin

Nodes := nil;

end;

destructor List.Done;

var

N : NodePtr;

begin

while Nodes <> nil do

begin

N := Nodes;

Dispose(N^.Item, Done);

Nodes := N^.Next;

Dispose(N)

end

end;

procedure List.Add(Item : PointPtr);

var

N : NodePtr;

begin

New(N);

N^.Item := Item;

N^.Next := Nodes;

Nodes := N

end;

procedure List.Report;

var

Current : NodePtr;

begin

Current := Nodes;

while Current <> nil do

begin

Str(Current^.Item^.GetX : 3, Temp);

OutTextLn('X = '+Temp);

Str(Current^.Item^.GetY : 3, Temp);

OutTextLn('Y = '+Temp);

Current := Current^.Next

end

end;

{Тело программы}

begin

InitGraph(GraphDriver, GraphMode,'');

if GraphResult <> GrOK then

begin

WriteLn('Остановка при графической ошибке: ',

GraphErrorMsg(GraphDriver));

Halt(1);

end;

HeapStatus ('Пространство в куче до распределения списка: ')

{ Создать список }

AList.Init;

{создание и пополнение списка несколькими фигурами за одну операцию}

AList.Add(New(ArcPtr, Init(151, 82, 25, 200, 330)));

AList.Add(New(CirclePtr, Init(400, 100, 40)));

AList.Add(New(CirclePtr, Init(305, 136, 5)));

{просмотр списка и определение координат фигур списка }

AList.Report;

HeapStatus ('пространство в “куче” после распределения списка: ');

{весь список освобждается одним вызовом деструктора}

AList.Done;

HeapStatus (‘пространство в “куче” после очистки списка:');

OutText('Нажмите ВВОД для завершения программы:‘);

ReadLn;

CloseGraph

end.

Рекомендации к упражнениям

Расширьте модуль Figures.pas, дополнив его объектами для линий, прямоугольников, квадратов, колец и т. п. Создайте круглую круговую диаграмму (в виде пирога с ломтиками), используя связанный список отдельных фигур–"ломтиков".

Реализуйте объекты с относительным положением. Относительное положение – это смещение от какой-нибудь основной (базовой) точки, выраженное либо отрицательной, либо положительной величиной. Точка с относительными координатами q,l будет находится на q точек растра левее своей базовой точки и на l точек вниз от базовой точки. Относительные положения могут понадобиться при компоновке фигур в более сложные.

Просмотрите свои библиотеки процедур и попробуйте “увидеть” в них объекты, затем перепишите процедуры в объектной форме. Библиотеки объектов будет гораздо легче использовать, что обусловлено возможностью расширения объектов без перекомпиляции предшествующей коллекции.

Начните знакомиться с имеющимися библиотеками по соответствующим руководствам и пытайтесь использовать имеющиеся в них средства при решении прикладных задач.

243

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]