Литмир - Электронная Библиотека

      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(PCivil, Init(1995, 'Мария', 'Рыбкина', 12));

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

{ В ходе распечатки вызывается метод ФАКТИЧЕСКОГО объекта }

for i:=1 to 3 do Persons[i]^.Report;

Readln;

end.

Сокрытие полей и методов

Объяснять ли вам, из чего строят современные программы? Из сотен «умных» объектов, которые образуют ветвистую иерархию родственных связей, открывающую простор полиморфизму.

Многие объекты фирменных библиотек – это полуфабрикаты, требующие лишь небольшой настройки под конкретное применение. В ходе такой настройки программист добавляет к базовому объекту свои поля и методы. И здесь порой случается то же, что при использовании библиотечных модулей: имя, назначенное программистом, может совпасть с уже объявленным именем в предке. И тогда имена могут конфликтовать. В библиотечных модулях эта проблема решается скрытием большей части переменных, процедур и функций в невидимой извне секции реализации IMPLEMENTATION.

Схожий прием используют и в объектном программировании. Поля и методы, доступ к которым наследникам не нужен, прячут в объекте-предке так, что они становятся невидимыми за пределами предка. И тогда спрятанные имена можно использовать в наследниках повторно по иному назначению. Не будет ли здесь путаницы? Нет, поскольку методы предка не знают о новых именах и обращаются к старым. А методы наследника не видят старых имен и обращаются к новым. Разумеется, что разработчик объекта-предка тщательно отбирает те поля и методы, что потребуются создателям потомков.

Сокрытие имен объекта организовано очень просто: в объявление объекта вставляют ключевые слова PRIVATE (личный) и PUBLIC (общедоступный). Эти слова разбивают объявление объекта на две части – приватную и общедоступную, например:

116
{"b":"596178","o":1}