Классы, экземпляры класса, наследование, иерархия.

Объектный тип (например, tDot) называется классом. Переменная данного типа — объект или, что то же самое, экземпляр класса. То есть при задании

Var dDot:tDot

переменная dDot – это объект.

tDot — тип-прародитель (класс-прародитель),

· tCircle=

object(tDot) {тип-наследник (класс-наследник)};

...

end;

· tRectangle=

object(tDot) {другой наследник};

...

end;

Тут tCircle и tRectangle — прямые потомки tDot (т.е. он их прямой прародитель) и т.п.

Далее можно определить другие классы-наследники:

· tTriangle=

object(tDot)

...

end;

· tFilledTriangle=

object(tTriangle)

...

end;

· tFilledCircle=

object(tCircle)

...

end;

· tRedCirlce=

object(tCirlce)

...

end;

· tGreenCirlce=

object(tCirlce)

...

end;

· tFilledRectangle=

object(tRectangle)

...

end;

 

Иерархия этих классов:

       
   
 
 

 

 


Суть наследования заключается в том, что потомки могут использовать поля данных и методы прародителей без определения этих полей и методов в классе потомка. Говорят, что поля данных и методы прародителей наследуются потомками.

Пример:

type

tDot=

object

active:Boolean;

X,Y:Integer;

procedure Show;

procedure Hide;

procedure MoveBy(dX,dY:Integer);

procedure MoveTo(NewX,NewY:Integer);

procedure Init(X_,Y_:Integer);

end;

tCircle=

object(tDot)

radius:Real;

procedure Show;

procedure Hide;

procedure MoveBy(dX,dY:Integer);

procedure MoveTo(NewX,NewY:Integer);

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

end;

var aDot: tDot;

aCircle: tCircle;

В теле объекта aCircle можно пользоваться полями X, Y, active, как если бы они были определены в tCircle, а не в tDot. При этом они никакого отношения не имеют к объекту aDot , т.е. к tDot.X, aDot.Y, aDot.Active. Это aCircle.X, aCircle.Y, aCircle.Active. Но по своей структуре они дублируют соответствующие поля объекта aDot.

А вот вызов методов 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.