Виртуальные методы и полиморфизм
Полиморфизм (от греч. "много форм") - это свойство классов, связанных наследованием, иметь различную реализацию входящих в них методов, и способность переменной базового класса вызывать методы того класса, объект которого содержится в этой переменной в момент вызова метода.
Полиморфизм используется в ситуации, когда для группы взаимосвязанных объектов требуется выполнить единое действие, но каждый из этих объектов должен выполнить указанное действие по-своему (т.е. у действия возникает много форм). Для этого определяется базовый для всех объектов класс с виртуальными методами, предусмотренными для меняющегося поведения, после чего эти методы переопределяется в потомках.
Для пояснения рассмотрим переопределение метода в подклассе:
type Base = class
public
procedure Print;
begin
writeln('Base');
end;
end;
Derived = class(Base)
public
procedure Print;
begin
writeln('Derived');
end;
end;
Присвоим переменной базового класса Base
объект производного
класса Derived
и вызовем метод Print
.
var b: Base := new Derived;
b.Print;
Какая версия метода Print
вызывается - класса Base
или класса Derived
?
В данном случае решение будет принято еще на этапе компиляции: вызовется метод
Print
класса Base
,
заявленного при описании переменной b
. Говорят, что имеет место
раннее связывание имени метода с его
телом. Если же решение о том,
какой метод вызывать, принимается на этапе выполнения программы в зависимости от
реального типа объекта, на который ссылается переменная b
, то
в данном случае вызывается метод Derived.Print
(говорят
также, что имеет место позднее связывание). Методы, для которых
реализуется позднее связывание, называются виртуальными,
а переменная базового класса, через которую осуществляется вызов виртуального
метода, - полиморфной переменной.
Таким образом, полиморфизм реализуется вызовом виртуальных функций через
переменную базового класса. Тип класса, который хранится в данной переменной на
этапе выполнения, называется динамическим типом
этой переменной.
Для того чтобы сделать метод виртуальным, следует в объявлении этого метода
после заголовка указать ключевое слово virtual
с последующей ;
. Для переопределения виртуального метода
следует использовать ключевое слово override
:
type
Base = class
public
procedure Print; virtual;
begin
writeln('Base');
end;
end;
Derived = class(Base)
public
procedure Print; override;
begin
writeln('Derived');
end;
end;
Теперь в аналогичном участке кода.
var b: Base := new Derived;
b.Print;
вызывается метод Print
класса
Derived
за счет того что решение о вызове метода откладывается на этап
выполнения программы.
Говорят, что методы Print
завязаны в
цепочку виртуальности. Чтобы
разорвать ее (не вызывать методы в подклассах виртуально) используется ключевое
слово reintroduce
:
type
DerivedTwice1 = class(Derived)
public
procedure Print;reintroduce
;
begin
writeln('DerivedTwice1');
end;
end;
Если мы хотим начать новую цепочку виртуальности, то следует использовать и
virtual
и reintroduce
:
type
DerivedTwice2 = class(Derived)
public
procedure Print;virtual;
reintroduce;
begin
writeln('DerivedTwice2');
end;
end;
Если переопределить виртуальную функцию невиртуальной без ключевого слова
reintroduce
, то ошибки не произойдет - будет выведено лишь
предупреждение о том, что цепочка виртуальности нарушена. Таким образом,
ключевое слово reintroduce
в этой
ситуации лишь подавляет вывод предупреждения.
При переопределении виртуального метода в подклассе его уровень доступа
должен быть не ниже, чем в базовом классе. Например, public
виртуальный метод не может быть переопределен в подклассе private
-методом.