Необходимость называть методы, выполняющие разные действия одинаковыми именами отнюдь не очевидна. Понимание, что это удобно, приходит с опытом. В программировании часто используют функции, в которых объекты являются параметрами, и в которых вызываются методы этих объектов. Причем, часто в качестве параметров используются и объекты-родители и объекты-наследники. Такая же ситуация возникает, если одни методы объекта вызываются внутри других методов.
Пример 9.5. Продемонстрируем удобство полиморфных методов, добавив для объекта-родителя Objt1 метод print(i:byte); который будет распечатывать рассчитанное методом Stepen значение. Новый метод будет наследоваться и выполняться и для объекта-наследника Objt2.
Решение.
Программный код методов, который полностью совпадает с примером 9.4. здесь опущен:
Program Ob4;
Type Objt1=OBJECT
x: Real;
n: Word;
procedure Init(x0:Real;n0: Word);
Function Stepen: Real;
procedure print(i:byte);
End;
Objt2=OBJECT(Objt1)
y: Real;
procedure Init(x0:Real;n0: Word;y0:Real);
Function Stepen: Real;
End;
procedure Objt1.Init(x0:Real;n0: Word);
begin
…
end;
Function Objt1.Stepen: Real;
var i : Word; p : Real;
Begin
…
End;
procedure Objt1.print(i:byte);
begin
if i=1 then write('Родитель: ') else write('Наследник:');
writeln(Stepen:6:2);
end;
procedure Objt2.Init(x0:Real;n0: Word;y0:Real);
begin
…
end;
Function Objt2.Stepen:Real;
Begin
…
End;
VAR O1 : Objt1; O2 : Objt2;
Begin
O1.Init(3,2);
O2.Init(3,2,0.5);
O1.print(1);
O2.print(2);
end.
Компилятор не найдет ошибок в такой программе. Однако, на этапе выполнения мы получим неправильные значения (сравните с предыдущим примером).
Ошибка произошла при работе с объектом-наследником, так как метод print был написан для объекта-родителя, и вызывает он метод Stepen объекта-родителя, даже в случае, когда он используется для наследника. Происходит это потому, что в программе были использованы статические методы.
Статические методы – методы, для которых связи с объектами устанавливаются на этапе компиляции.
Если заранее невозможно определить, какой из полиморфных методов (метод родителя или метод наследника) нужно будет использовать, необходимо определить виртуальный метод, для которого связь с объектом устанавливается во время выполнения программы.
Соответствующие процессы называются ранним и поздним связыванием. При использовании позднего связывания, каждый тип объекта имеет таблицу виртуальных методов (ТВМ). В этой таблице фиксируется тип объекта и адреса процедур и функций, реализующих виртуальные методы. Создание такой таблицы позволяет избежать описанных выше ошибок. Однако, при этом необходимо установить связь между конкретным экземпляром объекта и ТВМ. Это делает специальная процедура – конструктор. Конструктор заменяет процедуру инициализации начальных значений объекта.
Описание виртуальных методов и конструктора делается так (программный код, который полностью совпадает с примером 9.4 здесь снова опущен):
Program Ob4;
Type Objt1=OBJECT
x: Real;
n: Word;
Constructor Init(x0:Real;n0: Word); // описание процедуры конструктора
Function Stepen: Real; Virtual; // описание виртуального метода
procedure print(i:byte);
End;
Objt2=OBJECT(Objt1)
y: Real;
Constructor Init(x0:Real;n0: Word;y0:Real);
Function Stepen: Real; Virtual;
End;
Constructor Objt1.Init(x0:Real;n0: Word);
begin
x:=x0; n:=n0;
end;
Function Objt1.Stepen: Real;
var i : Word; p : Real;
Begin
…
End;
procedure Objt1.print(i:byte);
begin
writeln('Виртуальные методы: ');
if i=1 then write('Родитель: ') else write('Наследник:');
writeln(Stepen:6:2);
end;
Constructor Objt2.Init(x0:Real;n0: Word;y0:Real);
begin
Objt1.Init(x0,n0);
y:=y0;
end;
Function Objt2.Stepen:Real;
Begin
Stepen:=exp(y*ln(x));
End;
VAR O1 : Objt1; O2 : Objt2;
Begin
O1.Init(3,2);
O2.Init(3,2,0.5);
O1.print(1);
O2.print(2);
end.
Фактически, изменения коснулись только описания заголовков методов, но программа стала работать верно:
При описании виртуальных методов в объекте после заголовка процедуры или функции записывается служебное слово VIRTUAL;
Специальный метод Конструктор полностью тождественен процедуре, но служебное слово Procedure в нем заменяется на Constructor.
Виртуальные методы присоединяются к объекту не на этапе компиляции, а только при вызове конструктора. В последнем примере полиморфный метод Stepen является виртуальным. Выполнение конструктора Init приводит к подстановке вместо неопределенного метода Stepen конкретного метода характерного для данного объекта.
Объекты-потомки могут заменять родительские виртуальные методы только виртуальными, а родительские статические методы - только статическими. Если объект-потомок заменяет родительский виртуальный метод своим, то у нового метода должен быть точно такой же список параметров, как и у родительского. На статические методы это правило не распространяется.
Т.о. использование в объектах виртуальных методов предполагает, что потомки данного объекта будут изменять эти методы. Если изменение метода не ожидается, то он объявляется статическим.
В заключение приведем пример использования объектов (родителей и наследников) в качестве параметров независимых от них процедур:
Program Ob4;
Type Objt1=OBJECT
x: Real;
n: Word;
Constructor Init(x0:Real;n0: Word);
Function Stepen: Real; Virtual;
End;
Objt2=OBJECT(Objt1)
y: Real;
Constructor Init(x0:Real;n0: Word;y0:Real);
Function Stepen: Real; Virtual;
End;
Constructor Objt1.Init(x0:Real;n0: Word);
begin
x:=x0; n:=n0;
end;
Function Objt1.Stepen: Real;
var i : Word; p : Real;
Begin
p:=1;
FOR i:=1 TO n DO
p:=p*x;
Stepen:=p;
End;
Constructor Objt2.Init(x0:Real;n0: Word;y0:Real);
begin
Objt1.Init(x0,n0);
y:=y0;
end;
Function Objt2.Stepen:Real;
Begin
Stepen:=exp(y*ln(x));
End;
procedure print(i:byte;o:Objt1); // процедура основной программы
begin
writeln('Процедура print: ');
if i=1 then write('Родитель: ') else write('Наследник:');
writeln(o.Stepen:6:2);
end;
VAR O1 : Objt1; O2 : Objt2;
Begin
O1.Init(3,2);
O2.Init(3,2,0.5);
print(1,O1);
print(2,O2);
end.
Результаты работы этой программы: