А вот вызов методов aCircle. Show, aCircle. Hide и aCircle. Move(...) идет по-другому. Реально это tCircle. Show, tCircle. Hide и tCircle. Move(...), а методы для aDot — это tDot. Show, tDot. Hide и tDot. Move(...), "настроенные" на конкретный экземпляр класса, то есть "знающие", кого показывать, скрывать, перемещать.

Процедура Init переопределена так, что теперь надо задавать радиус, в отличие от прародителя:

Процедура Init в tCircle переопределена так, что теперь надо задавать радиус, в отличие от прародителя. Говорят, что она перекрывает соответствующий метод прародителя.

procedure tCircle. Init(X_,Y_,R_:Integer);

begin

tDot. Init(X_,Y_);

R:=R_;

end;

procedure tCircle. Hide;

var TmpColor:Word;

begin

...{нарисовать окружность цветом фона}

active:=false;

end;

procedure tCircle. MoveBy(dX, dY:Integer);

begin

Hide;

X:=X+dX;

Y:=Y+dY;

Show;

end;

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

begin

Hide;

X:=NewX;

Y:=NewY;

Show;

end;

Замечание: в принципе типы полей данных в потомках переопределять нельзя (по сравнению с прародителями). Все поля, имеющиеся у прародителей, имеются и у всех их потомков, плюс новые поля, которые дополнительно определены для потомков. Но начиная с Delphiи в Object PASCAL была введена возможность перекрытия полей данных.

Мы пока рассматриваем только так называемые статические методы. Они вызываются так, как скомпилированы для соответствующего типа; их можно переопределять в потомках. Но зато каждый раз надо дублировать текст: процедура Move для tCircle абсолютно аналогична процедуре Move для tDot. Если бы мы не описали процедуру tCircle. Move, благодаря наследованию вызвалась бы tDot. Move! И показалось бы перемещение точки, а не окружности. Существуют также виртуальные методы, которые позволяют обеспечить большую гибкость, чем статические. Но о них речь пойдет позже.

НЕ нашли? Не то? Что вы ищете?

Часто используют типы, называемые абстрактными, у которых единственное назначение — "растить" от них наследников "в разные стороны", чтобы иметь в наследниках соответствующие поля и методы. При этом изменение реализации такого класса автоматически "бесплатно" меняет поведение всех потомков. А вот интерфейс абстрактных классов стараются не менять, иначе надо переписывать почти всю программу — менять все вызовы соответствующих методов в потомках. У абстрактных классов не бывает экземпляров объектов (т. е. переменных такого типа — реально вызываемых объектов).

Пример:

type

tLocation=

object

X, Y:Integer;

procedure Init(X_,Y_:Integer);

function GetX:Integer;

function GetY:Integer;

end;

tDot=

object(tLocation)

active:Boolean;

procedure Init(X_,Y_:Integer);

procedure Show;

procedure Hide;

procedure MoveBy(dX, dY:Integer);

end;

тут tLocation — абстрактный тип; у него не будет экземпляров. Зато из любого потомка можно будет вызывать методы GetX и GetY.

Пример объектно-ориентированной программы: движение фигур по экрану.

unit Figures;

interface

uses…;

type

tLocation=

object

X, Y:Integer;

procedure Init(X_,Y_:Integer);

function GetX:Integer;

function GetY:Integer;

end;

tpDot=^tDot; {тип 'указатель на экземпляр класса tDot'. См. далее}

tDot=

object(tLocation)

aсtive:Boolean;

constructor Init(X_,Y_:Integer); {конструктор. См. далее}

destructor Done;virtual; {деструктор. См. далее}

procedure Show;virtual; {виртуальный метод. См. далее}

procedure Hide;virtual;

function IsActive:Boolean;

procedure MoveBy(dX, dY:Integer);virtual;

procedure MoveTo(newX, newY:Integer);virtual;

end;

tpCircle=^tCircle; {указатель на экземпляр tCircle}

tCircle=

object(tDot)

R:Integer;

constructor Init(X_,Y_:Integer;R_:Integer);

procedure Show;virtual;

procedure Hide;virtual;

procedure ChangeRadius(dR:Integer);virtual;

end;

{——— секция реализации ———}

implementation

{методы tLocation}

procedure tLocation. Init(X_,Y_:Integer);

begin

X:=X_;

Y:=Y_;

end;

function tLocation. GetX:Integer;

begin

GetX:=X;

end;

function tLocation. GetY:Integer;

begin

GetY:=Y;

end;

{методы tDot}

constructor tDot. Init(X_,Y_:Integer);

begin

tLocation. Init(X_,Y_);

active:=false;

end;

destructor tDot. Done;

begin

Hide;{это tDot. Hide для объекта класса tDot}

end;

procedure tDot. Show;

begin

… {нарисовали текущим цветом}

active:=true; {пометили состояние как активное}

end;

procedure tDot. Hide;

begin

… {нарисовали цветом фона}

active:=false; {пометили состояние как неактивное}

end;

function tDot. IsActive:Boolean;

begin

Result:=active;

end;

procedure tDot. MoveBy(dX, dY:Integer);

begin

Hide;{ это tDot. Hideдля объекта dDot, tCircle. Hideдля aCircle и т. д.}

X:=X+dX;

Y:=Y+dY;

Show; {tDot. Show для aDot, tCircle. Show для aCirle и т. д.

end;

procedure tDot. MoveTo(newX, newY:Integer);

begin

Hide;

X:=newX;

Y:=newY;

Show;

end;

{методы tCircle}

constructor tCircle. Init(X_,Y_,R_:Integer);

begin

tDot. Init(X_,Y_); {инициализация прародителя}

R:=R_;

end;

procedure tCircle. Show;

begin

…{ рисование окружности }

Active:=true;

end;

procedure tCircle. Hide;

var TmpColor:tColor;

begin

TmpColor:=; {запомнили текущий цвет рисования}

…{установили текущим цвет фона}

…{нарисовали окружность цветом фона, т. е. скрыли}

…{восстановили текущий цвет рисования}

Active:=false; {пометили состояние как неактивное}

end;

procedure tCircle. ChangeRadius(dR:Integer);

begin

Hide;

R:=R+dR;

if R<0 then R:=0; {радиус не может быть отрицательным}

Show;

end;

end.

В качестве примера использования модуля Figures напишем программу, в которой используются классы модуля Figures, а также добавляется наследник класса tCircle. При этом для него статический метод Init и виртуальные методы Show и Hide переписываются заново (как говорят — перекрываются), а все другие методы (например, moveBy) наследуются.

Unit Figure1; {добавление наследника}

Uses…,Figures; {используем модуль Figures}

type

tArc=

object(tCircle)

angle1,angle2:Integer;

construсtor Init(X_,Y_,R_,angle1_,angle2_:Integer);

procedure Show;virtual; {метод перекрывается}

procedure Hide;virtual; {также перекрывается}

end;

var

aArc:tArc;

aCircle:tCircle;

pArc:^tArc;

{методы Arc}

constructor tАrc. Init(X_,Y_,R_,angle1_,angle2_:Integer);

begin

tCircle. Init(X_,Y_,R_);

angle1:=angle1_;

angle2:=angle2_;

end;

procedure tArc. Show;

begin

…{рисуем дугу текущим цветом}

Active:=true;

end;

procedure tArc. Hide;

var TmpColor:tColor;

begin

TmpColor:=…; {запоминаем текущий цвет}

…{устанавливаем цвет фона текущим цветом рисования}

…{рисуем дугу цветом фона}

Active:=false; {помечаем состояние как неактивное}

…{восстанавливаем текущий цвет}

end;

Form1.Button1Click(…);{по нажатию кнопки идет отрисовка фигур}

begin

aCircle. Init(100,100,50); {инициализировали поля окружности}

aCircle. MoveTo(10);{чтобы остановить перемещение окружности и начать

двигать дугу нажмите <Enter>}

aArc. Init(120,190,25,0,90); {инициализировали поля дуги}

aArc. MoveBy (5,14); *****

end.

Виртуальные методы. Конструкторы. Раннее и позднее связывание. Полиморфизм.

В приведенной выше программе при описании объектов некоторые методы заданы как виртуальные (virutal). В этом случае перед вызовом какого-либо метода соответствующего экземпляра класса (переменной объектного типа) этот экземпляр (объект) должен быть инициализирован с помощью специального метода, называемого конструктор (constructor). Обычно в Borland Pascal ему давалось имя Init, а в Delphi - Create. Конструктор у объекта в языке Object PASCALможет быть не один, но у каждого из них должно быть свое имя. В C++ и в Java имя конструктора всегда совпадает с именем класса, но конструкторов также может быть несколько. При этом они должны отличаться списком параметров конструкторов. В зависимости от способа инициализации иногда бывает целесообразно вызывать тот или иной конструктор. Например, для окружности в ряде случаев можно задавать некое значение радиуса "по умолчанию". Допустим, это 50 пикселей. Тогда можно написать следующий конструктор (назовем его Init1):

constructor tCircle. Init1(X_,Y_:integer);

begin

Init(X_,Y_,50)

end;

При вызове такого конструктора надо задавать только координаты окружности.

В отличие от статических методов, где можно переопределять не только реализацию методов, но и их список параметров, виртуальные должны при переопределении в потомках иметь одну и ту же сигнатуру (список параметров и их типы). То есть виртуальные методы должны отличаться только реализацией.

Например, нельзя определить в tDot метод MyShow(X_,Y_:Integer);virtual, а в tCircle задать MyShow(X_,Y_,R_:Integer);virtual. Зато, благодаря виртуальным методам, благодаря наличию полиморфизма, можно не переписывать несколько раз тела этих методов, которые выглядят одинаково, но относятся к разным объектам. Например, вместо описания в классе tCircle метода MoveBy, буква в букву повторяющего метод MoveBy для класса tDot в случае статических методов Show и Hide:

procedure tCircle. MoveBy(dX, dY:Integer);

begin

Hide;

X=X+dX;

Y=Y+dY;

Show;

end;

и множества таких же (для tArc любого и нового типа фигуры), можно описать методы Show и Hide в иерархии как виртуальные, и сделать описание процедуры MoveBy только один раз для tDot, а для потомков использовать наследование. При этом виртуальные процедуры Hide и Show будут выполняться по-своему для своей фигуры, т. к. для виртуальных методов они берутся из класса, к которому относится вызывающий их объект, (например, из tCircle для объекта aCircle), а не из класса, в котором компилируется метод прародителя tDot для метода MoveBy. Методы Show и Hide, естественно, перекрываются в каждом классе-потомке, т. е. имеют разные реализации для разных фигур.

Надо отметить, что конструкторы в Turbo Pascal не могли быть виртуальными, хотя и могли наследоваться как обычные статические методы. В Object PASCAL начиная с Delphi 2.0 конструкторы можно объявлять виртуальными, хотя без особой необходимости не следует этого делать.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16