Файл: А.Г. Пимонов Основы объектно-ориентированного программирования в среде Турбо Паскаль.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 31.05.2024
Просмотров: 29
Скачиваний: 1
10
Type
TPoint = object(TLocation)
Clr : Byte; {Цвет}
Visib : Boolean; {Видимость}
Constructor Init (InitX, InitY:Word; InitColor:Byte); {Переопределяем метод инициализации – добавляем цвет}
Function GetColor : Byte; {Возвращает цвет}
Procedure Show; virtual; Procedure Hide; virtual;
Procedure IsVisib : Boolean;
Procedure ChangeColor(NewColor : Byte); {Меняет цвет} Procedure MoveTo(NewX, NewY:Word);
{Перемещает в новую позицию}
Destructor Done; virtual; end;
TCircle = object(TPoint)
R : Word;
Constructor Init(InitX, InitY, InitR : Word;
InitC : Byte);
Procedure Show; virtual;
Procedure Hide; virtual;
Destructor Done; virtual; end;
Объявление виртуального метода в каком-либо родительском объектном типе накладывает следующие ограничения на все его дочерние типы:
1)все методы дочерних типов, одноименные с виртуальными родительскими, обязаны быть также виртуальными (нельзя переопределить виртуальный метод статическим);
2)заголовки всех реализаций одного и того же виртуального метода должны быть полностью идентичными, включая количество формальных параметров и их типы;
3)каждый объектный тип, имеющий виртуальные методы, обязан иметь
конструктор.
Обратите внимание на то, что, кроме добавления слова virtual, из-
менилось и еще кое-что: при объявлении метода Init появилось незнакомое слово constructor, а метод Done превратился в destructor.
Дело в том, что таблица виртуальных методов изначально не содержит конкретных адресов точек входа. Перед использованием любого из вирту-
11
альных методов ее надо заполнить. Делает это специальный методконструктор. Метод-конструктор – это разновидность метода-процедуры. Синтаксически он отличается только использованием служебного слова constructor вместо procedure. Однако это приводит к тому, что при компиляции к этому методу добавляется так называемый пролог, код которого как раз и «расставляет» в ТВМ правильные адреса виртуальных методов.
Конструкторов в объекте может быть сколько угодно, один из конструкторов обязательно должен быть вызван перед вызовом первого виртуального метода (иначе программа попросту «зависнет»), и конструктор сам не может быть виртуальным.
Что касается другого «хитрого» метода – деструктора, то он не имеет никакого отношения к виртуализации методов, и зачем он нужен, мы расскажем позже. Отметим только, что деструктор в отличие от конструктора вполне может быть виртуальным.
2.5. Использование экземпляров объектов
Поскольку объект – это тип данных, то для работы с ним мы должны объявить переменную этого типа. Такая переменная в рамках ООП называется экземпляром объекта. Экземпляр объекта в Turbo Pascal может быть объявлен для любого объектного типа иерархии (как для родителя, так и для потомка). Более того, может быть объявлено несколько экземпляров одного и того же объекта. Каждый из таких экземпляров получает «свой» набор свойств. Они ничего «не знают» друг о друге, то есть являются полностью независимыми (по крайней мере, должны таковыми быть).
Экземпляр объекта описывается в разделе описания переменных программы (подпрограммы) следующим образом:
Var
<имя переменной (экземпляр объекта)>:<объектный тип>;
Объявлять можно как единичные экземпляры объектов, так и массивы объектов, записи, содержащие объекты, и даже объекты, содержащие объекты (правда, только в разделе описания типов Type). Единственное, чего нельзя
12
делать, это объявлять файлы объектов:
Var
a: Array [1..10] of TPoint; {Массив объектов – точек} b = record {Запись, содержащая два объекта}
p1,p2: TPoint;
end;
f : file of TPoint; {Файл объектов – так нельзя} Type
Rect = object {Объект, поля которого – объекты} a,b: TLocation;
…
end;
Для нашего примера имеет смысл описать экземпляр объекта TPoint, то есть точку на экране:
Var
Point1: TPoint;
После описания экземпляра объекта программе станут доступными его информационные поля и методы, то есть в программе можно читать и изменять значения полей экземпляра объекта, а также вызывать его методы. Обратиться к полю экземпляра объекта можно следующим способом: указать имя экземпляра, затем через точку имя поля. После чего с полем можно обращаться, как с обычной переменной, например присвоить ему какое-то значение:
Point1.X := 65;
Однако считается плохим стилем программирования прямое обращение к полям экземпляра объекта. Предполагается, что программа не должна ничего «знать» об атрибутах объекта, она только может «просить» экземпляр объекта показать или изменить свои атрибуты за счет использования соответствующих методов. Иначе говоря, программа может только вызывать методы экземпляра объекта. Вызов метода осуществляется синтаксически по тем же правилам, что и вызов «обычной подпрограммы», только перед именем метода надо обязательно написать через точку имя экземпляра объекта:
Point1.Init(150,100,Blue);
В этом примере мы создали объект – точку с координатами X=150 и Y=100 синего цвета (невидимую). Для «проявления» ее на экране необхо-
13
димо вызвать метод Show:
Point1.Show;
Для перемещения этой точки в новую позицию мы должны вызвать метод MoveTo, для получения текущих координат – GetX, GetY или
GetCoords и т.п.
2.6. Объекты, динамическая память и деструкторы
Экземпляры объектов можно размещать в динамической памяти (куче). Работа с кучей применительно к объектам почти не отличается от работы с кучей применительно к обычным данным. Точно так же необходимо объявить ссылку на объект (переменную ссылочного типа), выделить для нее память в куче, а в конце работы эту память освободить.
Разместим, например, в куче экземпляр объекта TCircle:
Type
PCircle = ^TCircle; Var
Circ1 : PCircle;
…
BEGIN
Circ1 := New(PCircle);
или
New(Circ1);
Для работы с экземпляром объекта, расположенного в куче, необходимо проводить (как обычно) операцию разыменовывания указателя, например:
Circ1^.Init(150,200,40,Blue);
Существует расширенный синтаксис процедуры (и функции) New для выделения памяти экземпляру объекта с одновременным вызовом методаконструктора:
Сirc1 := New(PCircle,Init(150,200,40,Blue));
или
New(Сirc1,Init(150,200,40,Blue));
При работе с динамическими объектами надо очень серьезно подходить к освобождению памяти. Ведь и сам объект в качестве своих полей может содержать указатели и ссылки. Память, занятая этими динамически-
14
ми данными, должна освобождаться до вызова процедуры Dispose для самого объекта. То есть если память, занятая полями, освобождается в методе Done, то этот метод должен быть вызван до удаления экземпляра объекта из кучи. То, что экземпляр объекта-родителя и экземпляр объекта-потомка являются совместимыми по типу «сверху вниз» (значение экземпляра объектапотомка может быть присвоено экземпляру объекта-предка с потерей «лишних данных», но не наоборот), при работе с кучей также может привести к неверному освобождению памяти (может быть освобождена «чужая» память). В связи с этим синтаксис процедуры Dispose тоже расширен и позволяет вызывать деструктор объекта до его удаления из кучи:
Dispose(Сirc1,Done);
При таком написании, сначала вызывается деструктор, а затем высвобождается память.
Кстати, назначение деструктора как разновидности процедуры – следить за тем, чтобы высвобождалась соответствующая память. То есть деструктор – это, как и конструктор, специальный вид метода, который подругому еще называют «сборщиком мусора».
2.7. Объекты и модули
Вышеперечисленные свойства объектов (наследование, полиморфизм) располагают к созданию библиотек объектов (объектных типов) с целью дальнейшего их использования. Библиотеки в Turbo Pascal реализуются в виде модулей (unit). Тем более, что структура описания объектов идеально соответствует структуре модуля: в модуле есть интерфейсная (декларативная) часть и раздел реализации, и при описании объектного типа мы сначала объявляем сигнатуры методов, а затем их реализацию.
Итак, для того чтобы разработать библиотеку объектов, мы создаем модуль, в интерфейсной части которого описываем сами объектные типы (те, которые будут доступны после подключения модуля), а в разделе реализации модуля приводим описания их методов.
Создадим модуль графических объектов GraphObj.
Unit GraphObj; {Модуль графических объектов}
15
INTERFACE {Интерфейсная часть} Type
PLocation = ^TLocation; TLocation = object
X,Y: Word; {Координаты позиции на экране}
Constructor Init(InitX, InitY: Word);
{Инициализация объекта}
Function GetX: Word; {Возвращает координату Х} Function GetY: Word; {Возвращает координату Y} Procedure GetCoords(Var CoordX, CoordY: Word); {Возвращает обе координаты}
Destructor Done; virtual; {Уничтожает объект}
End;
PPoint = ^TPoint;
TPoint = object(TLocation) Clr: Byte; {Цвет}
Visib: Boolean; {Видимость}
Constructor Init(InitX,InitY:Word; InitColor:Byte); {Переопределяем метод инициализации-добавляем цвет} Function GetColor: Byte; {Возвращает цвет}
Procedure Show; virtual;
Procedure Hide; virtual;
Function IsVisib: Boolean;
Procedure ChangeColor(NewColor:Byte);{Меняет цвет}
Procedure MoveTo(NewX, NewY: Word);
{Перемещает в новую позицию} Destructor Done; virtual;
end;
PCircle = ^TCircle;
TCircle = object(TPoint)
R: Word; {Радиус}
Constructor Init(InitX,InitY,InitR:Word;InitC:Byte);
Procedure Show; virtual;
Procedure Hide; virtual;
Destructor Done; virtual; end;
IMPLEMENTATION {Раздел реализации}
Uses Graph; {Подключаем графическую библиотеку} Constructor TLocation.Init;
Begin
X := InitX;
Y := InitY end;
Function TLocation.GetX: Word; begin
16
GetX := X end;
Function TLocation.GetY: Word; begin
GetY := Y end;
Procedure TLocation.GetCoords; begin
CoordX := GetX;
CoordY := GetY end;
Destructor TLocation.Done; begin
end;
Constructor TPoint.Init; begin
inherited Init(InitX, InitY);
Clr := InitColor; Visib := False
end;
Function TPoint.GetColor: Byte; begin
GetColor := Clr end;
Procedure TPoint.Show; begin
PutPixel(X, Y, Clr);
Visib := True end;
Procedure TPoint.Hide; begin
PutPixel(X, Y, GetBkColor); Visib := False
end;
Function TPoint.IsVisib: Boolean; begin
IsVisib := Visib end;
Procedure TPoint.ChangeColor; begin
Clr := NewColor;
If IsVisib then Show end;
Procedure TPoint.MoveTo;
Var St: Boolean;
17
begin
St := IsVisib; Hide;
X := NewX;
Y := NewY;
If St then Show end;
Destructor TPoint.Done; begin
Hide;
Clr := GetBkColor end;
Constructor TCircle.Init; begin
inherited Init(InitX, InitY, InitC); R := InitR
end;
Procedure TCircle.Show; begin
SetColor(Clr);
Circle(X, Y, R);
Visib := True end;
Procedure TCircle.Hide; begin
SetColor(GetBkColor); Circle(X, Y, R);
Visib := False end;
Destructor TCircle.Done; begin
inherited Done; R := 0
end;
{Пустая секция инициализации} END.
Модуль GraphObj создан и после компиляции будет готов к работе.
2.8. Объектно-ориентированные приложения
Использование ООП, естественно, не ограничивается только созданием библиотек графических объектов. Хороший стиль программирования предполагает создание объектов-программ (объектов-приложений). Такой
18
объект (назовем его Application) должен уметь выполнять три действия: инициализация (Init), выполнение основной работы (Run) и завершение работы (Done).
Создадим подобное приложение, которое будет «гонять» точку и окружность по экрану случайным образом до нажатия какой-либо клавиши. В приложении будут созданы экземпляры объектов TPoint и TCircle, причем экземпляр объекта TPoint мы разместим в области динамической памяти.
Program AppExample;
Uses Graph, CRT, GraphObj; Type
TApplication = object
Procedure Init;
Procedure Run;
Procedure Done; end;
Var
Application: TApplication; p: PPoint;
c: TCircle;
Procedure TApplication.Init; Var
D, R, MaxX, MaxY: Integer;
Begin
{Инициализация графического режима}
D := Detect;
InitGraph(D, R, 'C:\BP');
SetBkColor(White);
ClearDevice;
{Создаем точку и окружность} MaxX := GetMaxX;
MaxY := GetMaxY; Randomize;
New(p,Init(Random(MaxX), Random(MaxY),Red)); p^.Show; c.Init(Random(MaxX-40)+20,Random(MaxY-40)+20,20,Blue); c.Show
End;
Procedure TApplication.Run; Var
MaxX, MaxY: Integer; Begin
19
MaxX := GetMaxX;
MaxY := GetMaxY; Repeat
p^.MoveTo(Random(MaxX),Random(MaxY)); c.MoveTo(Random(MaxX-40)+20,Random(MaxY-40)+20);
Delay(4000)
Until KeyPressed End;
Procedure TApplication.Done; Begin
Dispose(p,Done);
c.Done;
CloseGraph
End;
BEGIN Application.Init;
Application.Run;
Application.Done
END.
Таким образом, любая объектно-ориентированная программа представляет собой совокупность взаимодействующих экземпляров объектов.
2.9. Совместимость объектных типов
Основное правило совместимости объектных типов состоит в том, что объекты дочерних типов могут свободно использоваться вместо родительских объектов, но не наоборот, т.е. совместимость расширяется по направлению от нижних уровней иерархии к верхним (от потомков к родителям). При этом любой дочерний тип наследует совместимость всех своих родительских типов. Для объектных типов различают совместимость трех видов:
1)между экземплярами объектов;
2)между указателями на экземпляры объектов;
3)между формальными и фактическими параметрами.
Экземпляру объекта родительского типа можно присвоить экземпляр
любого из его дочерних типов. Недопустимы обратные присваивания. В результате выполнения присваивания все информационные поля экземпляра объекта, стоящего слева от оператора присваивания, получают значения соответствующих полей экземпляра, стоящего справа.