Книжка Хабы (970988), страница 8
Текст из файла (страница 8)
Function GetY: Integer;
End;
Point = Object(Location)
Visible: Boolean;
End;
В качестве примера построим иерархию объектов Координаты - Точка - Квадрат (Lоcation - Point - Square). Удобно определять объекты в модулях, причем тип объекта описывается в интерфейсной части модуля, а тела процедур-методов типа объекта - в разделе модуля Implementation.
Пример 7.1. Этот модуль определяет объекты Lоcation, Point и Square.
unit obj1_OOP;
interface
uses graph;
type location = object
x,y: integer;
procedure init(X1,Y1:integer);
function getX: integer;
function getY: integer;
end;
point = object(location)
procedure init(X1,Y1:integer);
visible: boolean;
procedure show;
procedure hide;
procedure shift(X1,Y1:integer);
end;
square = object(point)
side: integer;
procedure init(X1,Y1,side1:integer);
procedure show;
procedure hide;
procedure shift(X1,Y1:integer);
procedure explode(Step:integer);
end;
{-----------------------------------------}
IMPLEMENTATION
procedure location.init(X1,Y1:integer);
begin
x:=X1;
y:=Y1;
end; {location.init}
function location.getX:integer;
begin
getX:=x;
end; {location.getX}
function location.getY:integer;
begin
getY:=y;
end; {location.getY}
procedure point.init(X1,Y1:integer);
begin
location.init(X1,Y1);
end; {point.init}
procedure point.show;
begin
visible:=true;
putpixel(x,y,getcolor);
end; {point.show}
procedure point.hide;
begin
visible:=false;
putpixel(x,y,getbkcolor);
end; {point.hide}
procedure point.shift(X1,Y1:integer);
begin
hide;
init(X1,Y1);
show;
end; {point.shift}
procedure square.init(X1,Y1,side1:integer);
begin
side:=side1;
point.init(X1,Y1);
end; {square.init}
procedure square.show;
begin
visible:=true;
rectangle(X-side div 2, X- side div 2, X+side div 2, Y+ side div 2);
end; {sguare.show}
procedure square.hide;
var temp:word;
begin
temp :=getcolor;
setcolor(getbkcolor);
show;
visible:=false;
Setcolor(temp);
end; {sguare.hide}
procedure square.shift(X1,Y1:integer);
begin
hide;
X:=X1;
Y:=Y1;
show;
end; {sguare.shift}
procedure square.explode(step:integer);
begin
hide;
inc(side,step);
show;
end;
end.
Чтобы использовать эти типы объектов и методы, в своей программе достаточно определить экземпляры типа Point и Square:
program ex1_OOP;
uses crt,graph,obj1_OOP;
var gm,gd :integer;
XP :point;
XS :square;
I :word;
Begin
gd:=detect;
InitGraph(gd,gm,'C:\BP\BGI');
if GraphResult<>GrOk then Halt(1);
with XP do
begin
init(100,100);
show;
readln;
shift(200,200);
readln;
hide;
end;
with XS do begin
init(100,100,50);
show;
readln;
shift(200,200);
readln;
for i:= 1 to 20 do
begin
explode(10);
delay(200);
end;
readln;
hide;
end;
closeGraph;
End.
В этом примере инициализируется точка, затем она показывается на экране, перемещается в новое место и прячется. Затем инициализируется квадрат, показывается на экране, в 20 раз увеличивает свои линейные размеры на 5 точек и стирается.
Когда родительский тип определен, наследуемые правила могут быть заменены (а могут и использоваться). Для замены унаследованного правила просто определите новое правило с тем же именем, как и унаследованное, но с другим телом и (при необходимости) с другим набором параметров. Логика компилятора в решении вызовов методов такова: компилятор сначала ищет метод с таким именем, определенный в пределах типа объекта. Тип Square определяет методы с именами Init, Show, Hide, Shift и Explode. Если тип Square должен вызвать один из этих методов, компилятор заменит этот вызов адресом одного из собственных методов объекта Square.
Если методов с таким именем не определено в пределах типа объекта, компилятор переходит вверх, к типу непосредственного прародителя, и ищет метод, имя которого вызвано в пределах этого типа. Если метод с таким именем найден, адрес метода прародителя заменяет имя в исходном коде метода потомка. Если же метод по такому имени не найден, компилятор продолжит поиск метода вверх до следующего прародителя. Если компилятор попадает в самый верхний (первый) тип объекта, он выдает сообщение об ошибке, показывающее, что такой метод не определен.
Пользователь может создавать новых потомков и не имея исходного кода Unit (это важно в коммерческих приложениях). Следующий пример иллюстрирует эту возможность, порождая новый объект PaintSquare (закрашенный квадрат), базируясь только на тексте секции Interface для модуля Obj1_OOP.
Unit obj2_OOP;
Interface
Uses Graph, obj1_OOP;
Type PaintSquare = Object(Square)
SquareColor: Word;
Procedure Init(X1,Y1,Side1,Color:Integer);
Procedure Show;
Procedure Hide;
Procedure Shift(X1,Y1:Integer);
Procedure Explode(Step:Integer);
End;
Implementation
Procedure PaintSquare.Init(X1,Y1,Side1,Color:Integer);
Begin
Square.Init(X1,Y1,Side1);
SquareColor :=Color;
End;
Procedure PaintSquare.Show;
Var Temp : Word;
Begin
Square.Show;
Temp:=GetColor;
SetFillStyle(SolidFill, SquareColor);
FloodFill(X,Y,Temp);
SetColor(Temp);
End;
Procedure PaintSquare.Hide;
Begin
SetFillStyle(SolidFill,GetBkColor);
FloodFill(X,Y,GetBkColor);
Square.Hide;
End;
Procedure PaintSquare.Shift(X1,Y1 : Integer);
Begin
Hide;
Init(X1,Y1,Side,SquareColor);
Show;
End;
Procedure PaintSquare.Explode(Step : Integer);
Begin
Hide;
Inc(Side,Step);
Init(X,Y,Side,SquareColor);
Show;
End;
End.
Используется модуль аналогично предыдущему. В следующем примере инициализируется закрашенный квадрат, затем он показывается на экране, перемещается на новое место и, наконец в 20 раз увеличивает линейные размеры на 5 точек, а затем возвращается в исходное состояние путем последовательного уменьшения.
Program EX2_OOP;
Uses Crt, Graph, obj2_OOP;
var gd,gm : integer;
XPS : PaintSquare;
I : Word;
begin
gd:=Detect;
InitGraph(gd,gm,'C:\BP\BGI');
If graphResult<>GrOk then Halt(1);
With XPS do
Begin
Init(100,100,50,14);
Show;
Readln;
Shift(200,200);
Readln;
For i:=1 to 20 do Begin
Explode(5);
Delay(200);
End;
Readln;
For i:=1 to 20 do Begin
Explode(-5);
Delay(200);
End;
Readln;
CloseGraph;
End;
End.
1.5. Виртуальные методы и полиморфизм
Все методы, которые мы рассматривали до этого, были статическими, т.е. компилятор размещает их и разрешает все ссылки во время компиляции. В Турбо Паскале существует возможность связывания данных с методами на этапе выполнения программы, такое связывание называется поздним. При позднем связывании используются так называемые виртуальные методы. Виртуальные методы реализуют чрезвычайно мощное средство для обобщения, которое называется полиморфизмом.
Полиморфизм - свойство, позволяющее называть разные алгоритмические действия одним именем. Такое действие совместно используется в иерархии объектов, причем каждый объект в этой иерархии реализует это действие своими собственными, пригодными для него способами.
Метод делается виртуальным, когда за его определением в типе объекта ставится служебное (зарезервированное) слово Virtual. Надо запомнить, что, если вы определяете метод в родительском типе как Virtual, все методы с тем же именем в любом из потомков также должны быть описаны как Virtual.
Вот как может произойти виртуализация рассмотренных выше графических объектов:
Unit obj3_OOP;
Interface
Uses Graph;
Type Location = Object
X,Y : Integer;
Procedure Init(X1,Y1 : Integer);
Function GetX : Integer;
Function GetУ : Integer;
End;
Point = Object(Location)
Visible : Boolean;
Constructor Init(X1,Y1 : integer);
Procedure Show; Virtual;
Procedure Hide; Virtual;
Procedure Shift(X1,Y1 : integer); Virtual;
Destructor LastStep; Virtual;
End;
Square = Object(Point)
Side : integer;
Constructor Init(X1,Y1,Side1 : Integer);
Procedure Show; Virtual;
Procedure Hide; Virtual;
Destructor LastStep; Virtual;
End;
PaintSquare = Object(Square)
SquareColor : Word;
Constructor Init(X1,Y1,Side1,Color : Integer);
Procedure Show; Virtual;
Procedure Hide; Virtual;
Destructor LastStep; Virtual;
End;
IMPLEMENTATION
Procedure Location.Init(X1,Y1 : integer);
Begin
X:=X1;
Y:=Y1;
End;
Function Location.GetX : Integer;
Begin
GetX:=X;
End;
Function Location.GetY : Integer;
Begin
GetY:=Y;
End;
Constructor Point.Init(X1,Y1 : Integer);
Begin
Location.Init(X1,Y1);
End;
Procedure Point.Show;
Begin
Visible:=True;
PutPixel(X,Y,GetColor);
End;
Procedure Point.Hide;
Begin
Visible:=False;
PutPixel(X,Y,GetBkColor);
End;
Procedure Point.Shift(X1,Y1: Integer);
Begin
Hide;
X:=X1;Y:=Y1;
Show;
End;
Destructor Point.LastStep;
Begin
End;
Constructor Square.Init(X1,Y1,Side1 : integer);
Begin
Side:=Side1;
Point.Init(X1,Y1);
end;
Procedure Square.Show;
Begin
Visible:=True;
Rectangle(X-Side div 2, Y-Side div 2,
X+Side div 2, Y+Side div 2);
End; {Show}
Procedure Square.Hide;
Var Temp : Word;
Begin
Temp:=GetColor;
SetColor(GetBkColor);
Show;
Visible:=False;
Setcolor(Temp);
End; {hide}
Destructor Square.LastStep;
Begin
End;
{-------------------------------------------}
Constructor PaintSquare.Init(X1,Y1,Side1,Color:Integer);
Begin
Square.Init(X1,Y1,Side1);
SquareColor:=Color;
End;
Procedure PaintSquare.Show;
Var Temp : Word;
Begin
Square.Show;
Temp:=GetColor;
SetFillStyle(SolidFill, SquareColor);
FloodFill(X,Y,Temp);
SetColor(Temp);
End;
Procedure PaintSquare.Hide;
Begin
SetFillStyle(SolidFill,GetBkColor);
FloodFill(X,Y,GetBkColor);
Square.Hide;
End;
Destructor PaintSquare.LastStep;
Begin
End;
End.
Прежде всего, отметим, что метод Shift уходит из определения типов объекта Square и объекта PainSquare . Он наследуется от Point, и все вложенные вызовы методов переходят к Square и PaintSquare.
Отметим также использование нового служебного слова Constructor (конструктор), которое заменило зарезервированное слово Procedure для Point.Init, Square.Init и PainSquare.Init. Все типы объектов, которые имеют виртуальные правила, должны иметь конструктор. Конструктор - это особый вид процедуры, который выполняет некоторую установочную работу для механизма виртуальных методов. Каждый отдельный экземпляр объекта должен быть инициализирован отдельным вызовом конструктора.