Полиморфизм позволяет использовать одинаковые имена для методов, входящих в различные классы. Принцип полиморфизма обеспечивает в случае обращения к одноименным методам выполнение того из них, который соответствует классу объекта.
Пусть, например, мы решили создать новый класс, отличающийся от родительского класса тем, что в каком-то из его методов изменен алгоритм. В этом случае нам необходимо перекрыть в классе-потомке соответствующий метод, то есть объявить в классе-потомке одноименный метод и записать в нем нужный алгоритм. В результате мы получим два класса, имеющих одноименные методы, которые выполняются по-разному. Полученные классы будут полиморфными.
Поля
Полями называются инкапсулированные в классе данные. Поля класса подобны полям записи, но в отличие от них могут быть любого типа, в том числе классами, например:
type
TChildClass = class
FOne: Integer;
FTwo: String;
FThree: TObject;
end;
Исходя из принципа инкапсуляции, обращение к полям должно осуществляться при помощи методов и свойств класса. Вместе с тем, в Object Pascal допускается обращаться к полям и непосредственно. Для того чтобы обратиться к полю, необходимо записать составное имя, состоящее из имени класса и имени поля, разделенных точкой, например:
var
MyObject: TChildClass;
begin
MyObject.FOne:= 16;
MyObject.FTwo:= 'Некоторое строковое значение';
end;
Заметим, что обычно имя поля такое же, как и имя соответствующего свойства, но к имени поля в качестве первой буквы добавляют букву F.
Методы
Методами называются инкапсулированные в классе процедуры и функции.
Например:
type
TChildClass = class
FOne: Integer;
FTwo: String;
FThree: TObject;
function FirstFunc(x:real):real;
procedure SecondProc;
end;
Для того чтобы обратиться к методам, как и для полей, необходимо использовать составные имена:
var
MyObject: TChildClass;
у: real;
begin
MyObject.SecondProc;
у:= MyObject.FirstFunc(3.14);
end;
Методы, определенные в классе, могут быть статическими, виртуальными, динамическими или абстрактными. Тип метода определяется механизмом перекрытия его в потомках.
Для статических методов перекрытие осуществляется компилятором. Например, пусть у нас имеется описание родительского класса TBase и его потомка TDescedant, содержащих одноименный метод MyJoy:
type
TBase = class
procedure MyJoy;
end;
TDescedant = class(TBase)
procedure MyJoy;
end;
var
FirstObject: TBase;
SecondObject: TDescedant;
begin
FirstObject.MyJoy;
SecondObject.MyJoy;
end;
В соответствии с принципом полиморфизма в операторе FirstObject.MyJoy;
вызывается метод, описанный в классе TBase, а в операторе SecondObject.MyJoy;
вызывается метод, описанный в классе TDescedant.
По умолчанию все методы, описанные в классе, являются статическими. Динамические и виртуальные методы отличаются от статических тем, что замещение родительских методов, методами потомков происходит на этапе выполнения программы. Для объявления виртуального метода в родительском классе необходимо использовать зарезервированное слово virtual, а для объявления динамического метода - зарезервированное слово dynamic. В классе-потомке в заголовке замещающего метода должно быть указано зарезервированное слово override. Например:
type
TBase = class
procedure MyJoy; virtual;
end;
TDescedant = class(TBase)
procedure MyJoy; override;
end;
var
FirstObject: TBase;
SecondObject: TDescedant;
begin
FirstObject.MyJoy;
SecondObject.MyJoy;
end;
Если бы мы захотели, чтобы метод MyJoy в классе TBase был динамическим, слово virtual в заголовке процедуры следует заменить словом dynamic. Различие между виртуальными и динамическими методами невелико и связано с особенностями реализации их вызовов. Можно сказать, что виртуальные методы более эффективны с точки зрения затрат времени, а динамические методы позволяют более рационально использовать оперативную память.
Абстрактными называются виртуальные или динамические методы, которые определены в классе, но не содержат никаких действий, никогда не вызываются и обязательно должны быть переопределены в классах-потомках. Объявляется абстрактный метод при помощи зарезервированного слова abstract, расположенного после слов virtual или dynamic, например:
procedure MyMetod; virtual; abstract;
Основное предназначение абстрактных методов - быть родоначальником иерархии конкретных методов в классах-потомках.
В любом классе содержатся два специальных метода — конструктор и деструктор. Эти методы содержатся в классе-родоначальнике всех остальных классов - TObject и, следовательно, наследуются потомками. Как и другие методы, они могут быть изменены в классах-потомках, т. е. перекрыты. В классе TObject и в большинстве его потомков конструктор и деструктор называются Create и Destroy соответственно.
Конструкторы предназначены для создания и инициализации объекта. Дело в том, что объект в языке Object Pascal является динамической структурой и переменная-объект содержит не сами данные, а ссылку на них. Конструктор распределяет объект в динамической памяти и присваивает полям объекта начальные значения. При этом поля порядковых типов в качестве начального значения получают 0, строкового — пустую строку, поля-указатели - значение nil, поля-варианты — Unassigned. Кроме того, конструктор помещает ссылку на созданный объект в переменную Self, которая автоматически объявляется в классе. Из сказанного следует, что обращение к полям, свойствам и методам объекта должно осуществляться только после вызова конструктора. Деструктор освобождает динамическую память и разрушает объект. Для объявления конструктора и деструктора используются зарезервированные слова constructor и destructor соответственно. Например:
type
TSample = class
Text:string;
constructor Create;
destructor Destroy;
end;
Для того чтобы создать объект, необходимо применить метод-конструктор к классу объекта:
var
MyObject: TSample;
begin
MyObject:= TSample.Create;
end;
Если создается класс-потомок и при его создании планируется осуществить некоторые дополнительные действия, отсутствующие у класса-родителя, то в конструкторе класса-потомка следует сначала вызвать конструктор своего родителя, а уже потом осуществлять дополнительные действия. Вызвать любой перекрытый метод родительского класса можно при помощи зарезервированного слова inherited (унаследованный). Например, если в классе TDescedant имеется свой собственный конструктор
type
TDescedant = class(TBase)
FMark: Boolean;
constructor Create(Mark:Boolean);
end;
то его реализация могла бы быть такой:
constructor TDescedant.Create(Mark:Boolean);
begin
inherited Create;
FMark:= Mark;
end;
где вызов родительского конструктора осуществляется оператором
inherited Create;
а оператор
FMark := Mark;
осуществляет дополнительные действия.
Кроме деструктора Destroy, в базовом классе TObjec t определен метод Free, который прежде проверяет, был ли объект на самом деле реализован и только потом вызывает метод Destroy. Если объект не был создан конструктором, то обращение к деструктору, приведет к генерации исключительной ситуации. Следовательно, для уничтожения ненужного объекта удобнее использовать метод Free, например:
MyObject.Free;