Песни о Паскале — страница 100 из 112


procedure TMilitary.Report;

begin

      inherited Report;       { вызов метода предка }

      Writeln('Воинское звание: '+mRank);

end;


Породив «военного человека», возьмёмся за мирное строительство, создадим объект, исполняющий роль гражданского служащего. Назовем его TCivil, а род его пойдет от того же предка TPerson. У гражданских своя гордость и своя служебная лестница, ступеньки которой – категории – нумеруются числами. Хранить информацию о карьерном росте будем в числовом поле, назовем его mLevel – «уровень». Так же, как и для военного, нам придется дополнить конструктор объекта и метод распечатки Report. Ход рассуждений будет прежним, а потому не буду повторять его, сделайте эту работу сами.

Сотворив наследников «человека» – объекты TMilitary и TCivil, мы почти разобрались в механизме наследования. А где же полиморфизм? В чем он проявляется? Для ответа обратимся к динамическим объектам.

Динамические объекты

Динамические переменные знакомы нам с 52-й главы. Указатели на объекты ничем не отличаются от таковых для других типов данных. Например, указатель на тип TPerson объявляется так:


type PPerson = ^TPerson;


Теперь можно объявить переменную этого типа, взять для неё память в куче, а затем инициализировать поля конструктором.


var P : PPerson;       { указатель на объект }

begin

      New(P);       { выделение памяти в куче }

      P^.Init(1985, 'Иван', 'Грозный'); { инициализация объекта }


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


      P:= New(PPerson, Init(1985, 'Иван', 'Грозный'));


Обратите внимание, что первый параметр функции – это тип-указатель PPerson, а не тип объекта TPerson!

Примечание. В языке Delphi и совместимом с ним режиме Free Pascal применяют иной синтаксис вызова конструктора, например:


var P : TPerson;       { это указатель на объект! }

...

P:= TPerson.Init(1985, 'Иван', 'Грозный'); { создается динамический объект }


Дело в том, что все объекты в Delphi – это динамические переменные, и переменная типа TPerson является указателем на объект. Для создания таких объектов применяют не функцию New, а вызов конструктора с префиксом, совпадающим с названием типа объекта.

Полиморфизм

Теперь, после знакомства с динамическими объектами, вернемся к полиморфизму. Предположим, что в программе объявлены указатели трех типов.


var P1 : PPerson; { указатель на предка }

      P2 : PMilitary; { указатель на потомка }

      P3 : PCivil;       { указатель на потомка }


Здесь P1 является указателем на предка, а P2 и P3 – на разных его потомков. Отчасти полиморфизм состоит в том, что указателю на предка разрешено присваивать указатели на любого его потомка, то есть следующие операторы не вызовут протеста компилятора.


      P1:= P2;

      P1:= P3;


Скажете, пустая формальность? Зря вы так! Воистину здесь скрыт глубокий смысл, поскольку через указатель на предка можно вызывать методы его потомков. Но при условии, что эти методы унаследованы от предка как виртуальные. Так, в следующем примере указателю P1 трижды присваиваются указатели на объекты разных типов: сначала на предка TPerson, а затем на двух его потомков, после чего всякий раз вызывается виртуальный метод Report. Но в реальности происходит вызов трех разных методов Report – соответственно типу объекта, на который в текущий момент ссылается указатель P1. Так срабатывает механика полиморфизма!


      P1:= New(PPerson, Init(1985, 'Иван', 'Грозный'));

      P1^.Report;       { вызывается TPerson.Report }

      P1:= New(PCivil, Init(1995, 'Мария', 'Рыбкина', 12));

      P1^.Report;       { вызывается TCivil.Report }

      P1:= New(PMilitary, Init(1985, 'Андрей', 'Быков', 'Майор'));

      P1^.Report;       { вызывается TMilitary.Report }


Кажется, что полиморфизм одушевляет объект и делает его умнее: объект сам «понимает», как ему исполнить то, или иное желание программиста. Тот лишь вызывает нужный метод, не вникая в детали. Это похоже на управление телевизором или другим прибором. Стоит подать напряжение, и все они включатся: хоть и по-разному, но каждый по-своему правильно.

Но мощная механика полиморфизма срабатывает лишь для родственных объектов, состоящих в отношении предок-потомок. Именно в таких отношениях находятся созданные нами объекты. А вот пример иного рода.


type TA = object

      constructor Init;

      procedure Report; virtual;

      end;


      TB = object

      constructor Init;

      procedure Report; virtual;

      end;


Здесь объявлены два типа объектов с одноименными виртуальными методами. Но полиморфизмом тут и не пахнет, поскольку объекты не родственны меж собой!

В завершение темы изучите программу «P_61_3», где собрано все, что было сказано о «человечьих» объектах.


{ P_61_3 – Демонстрация принципов наследования и полиморфизма }


uses Person;       { Объект TPerson импортируется из модуля Person }


type PMilitary = ^TMilitary; { указатель на объект «ВОЕННОСЛУЖАЩИЙ» }

      TMilitary = object (TPerson)

      mRank : string; { воинское звание }

      constructor Init(aBearing: integer; const aName, aFam,

      aRank : string);

      procedure Report; virtual;

      end;

      PCivil = ^TCivil; { указатель на объект «ГРАЖДАНСКИЙ СЛУЖАЩИЙ» }

      TCivil = object (TPerson)

      mLevel : integer;       { должностная категория }

      constructor Init(aBearing: integer; const aName, aFam : string;

      aLevel: integer);

      procedure Report; virtual;

      end;


      {--- Реализация объекта «ВОЕННОСЛУЖАЩИЙ» ---}


constructor TMilitary.Init(aBearing: integer; const aName, aFam,

      aRank : string);

begin

inherited Init(aBearing, aName, aFam);

mRank:= aRank;

end;


procedure TMilitary.Report;

begin

inherited Report;

Writeln('Звание: '+mRank);

end;

      {--- Реализация объекта «ГРАЖДАНСКИЙ СЛУЖАЩИЙ» ---}

constructor TCivil.Init(aBearing: integer; const aName, aFam : string;

      aLevel: integer);

begin

inherited Init(aBearing, aName, aFam);

mLevel:= aLevel;

end;


procedure TCivil.Report;

begin

inherited Report;

Writeln('Категория: ', mLevel);

end;


var Persons : array[1..3] of PPerson; { массив указателей на ПРЕДКА }

i : integer;


begin       {--- Главная программа ---}

{ Массив заполняется объектами РАЗНЫХ, но родственных типов }

Persons[1]:= New(PPerson, Init(1985, 'Иван', 'Семенов'));

Persons[2]:= New(