При наследовании выполняется очень интересный (и довольно сложный для понимания) принцип полиморфизма. Дело в том, что в родительском объекте и объекте-потомке могут быть методы с одинаковыми именами, но разной реализацией. Паскаль автоматически определяет, какой метод вызывать.
Например:
type TCar = Class
procedure Move;
end;
Ttruck = CLASS(TCar)
procedure Move;
end;
Здесь и у объекта-родителя Tcar, и у объекта-потомка Ttruck есть метод с названием Move. Внутренний код этих методов разный. Как же Паскалю определить, какой метод вызывать?
Очень просто – по типу переменной-объекта:
var Car: TCar;
truck: Ttruck;
begin
truck:=TTuck.Create;
Car:=TCar.Create;
{ вызовется метод Move класса Ttruck }
truck.Move;
{ вызовется метод Move класса Tcar }
Car.Move; . .
В данном примере есть два объекта: Car типа Tcar и truck типа Ttruck. При вызове метода Move объекта Car будет выполняться код, относящийся к объектному типу Tcar, а при вызове метода Move объекта Truck - относящийся к объектному типу Ttruck.
Зачем нужен полиморфизм? Прежде всего - для удобства программиста. Во всех сходных по смыслу объектах методы, выполняющие одни и те же операции (хотя и разными способами) называются одинаково.
А как быть, если нужно вызвать из объекта-потомка на выполнение родительский метод, а в объекте-потомке уже есть метод с таким именем? Такая задача возникает довольно часто. Рассмотрим метод "Начало движения" для легковой машины и самосвала. Очевидно, самосвалу сначала нужно опустить кузов, если он поднят, и поднять упоры. Далее же пуск легкового автомобиля и самосвала будут происходить одинаково. Было бы логично вынести эту одинаковую часть "вверх", в метод "Начало движения" объекта-родителя, а для самосвала в методе "Начало движения" сначала выполнить ряд дополнительных действий, а затем вызвать метод родителя – там уже написано все остальное.
Если у объекта есть родитель, можно вызывать его методы, совпадающие по названию с методами самого объекта, при помощи оператора INHERITED:
PROCEDURE Ttruck.Move; BEGIN { специфические для самосвала действия }
{вызывается метод родителя} INHERITED Move;
… END;
Часто производные объектные типы на основе некоторого базового типа создаются другими программистами. Автор базового типа не может знать заранее, подойдут ли его реализации методов всем желающим. А вдруг кому-то понадобится моделировать не автомобиль с двигателем внутреннего сгорания, а трамвай, у которого тоже есть почти все свойства автомобиля, но процедура запуска двигателя совершенно другая?
В таком случае создатель базового метода может немного схитрить, облегчив работу и себе, и людям. Не нравится вам моя реализация метода "Начало движения" – пишите свою, пусть она перекроет то, что написал я.
Методы, которые можно перекрыть одноименными методами объектов-потомков, называются виртуальными(Рис. 6.2).
Рис. 10.2. Виртуальные методы.
Например:
TYPE Tcar=CLASS PROCEDURE Move; VIRTUAL; { этот метод можно перекрыть } END; … TYPE Ttruck=CLASS(Tcar) PROCEDURE Move; OVERRIDE; { перекрытие метода Move } END;
А вот в производном объекте для перекрытия одноименного виртуального метода нужно указать слово OVERRIDE (перекрыть). Тогда при выполнении следующего фрагмента программы:
VAR Car:Ttruck; … Car:=TTruck.Create; Car.Move
будет вызван метод Move объекта Ttruck – в полном соответствии в принципом полиморфизма.
Бывают случаи, когда реализация одного и того же метода в базовом и производном объектном типах настолько различаются, что нет смысла расписывать реализацию метода базового типа. Достаточно указать, что у объекта может бытьметод с указанным именем.
Методы, не имеющие реализации, называются абстрактными. Поскольку абстрактный метод обязательно должен перекрываться настоящим (с реализацией) методом в объекте-потомке, все абстрактные методы должны одновременно быть и виртуальными. Записывается это так:
TYPE Tcar=CLASS PROCEDURE Move;VIRTUAL;ABSTRACT; END;
Реализации метода Tcar.Move в программе вообще нет. Соответственно, и вызвать на исполнение абстрактный метод в базовом объекте нельзя – исполнять-то нечего! Следующая запись вызовет ошибку:
VAR c:Tcar; c:=TCar.Create;
c.Move
Особым случаем применения виртуальных методов следует считать их использование в конструкторе объекта. При создании любого объекта в Delphi, если родитель объекта явно не указан, он считается потомком некоего виртуального объектного типа Tobject. Следующие фрагменты программы эквивалентны:
TYPE TA=CLASS
и
TYPE TA=CLASS(TObject)
Если объект не требует какой-то специфической инициализации, то при создании переменной-объекта достаточно вызвать метод Create, унаследованный от объекта Tobject. Иначе обстоит дело при написании своего собственного конструктора. Рассмотрим пример:
TYPE TA=CLASS
a:BYTE;
CONSTRUCTOR Create;
END;
VAR aa:TA;
constructor TA.Create(x:byte);
begin
inherited Create;
self.a:=x
end;
В объекте TA создан метод-конструктор с именем Create, который перекрывает одноименный метод родительского объекта TObject. Однако именно метод родительского объекта вызывать следует обязательно: он выделяет память под объект. Поэтому в теле процедуры-конструктора оператором INHERITED сразу вызывается метод Create объекта-родителя. После выполнения родительского метода объект становится проинициализированным и можно задавать начальные значения его свойств.