§ 9.7. Статические и виртуальные методы

 

        Необходимость называть методы, выполняющие разные действия одинаковыми именами отнюдь не очевидна. Понимание, что это удобно, приходит с опытом. В программировании часто используют функции, в которых объекты являются параметрами, и в которых вызываются методы этих объектов. Причем, часто в качестве параметров используются и объекты-родители и объекты-наследники. Такая же ситуация возникает, если одни методы объекта вызываются внутри других методов.

 

Пример 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.

 

        Результаты работы этой программы: