Constructor Create (a, b, R: integer); procedure Draw (NewX, NewY : integer); end; var
OnePoint : TPoint; OneCircle : TCircle; implementation
Procedure TPoint.Draw (Newx, Newy: integer); begin
Forml.Canvas.Pixels [X,Y] : = clBtnFace; X:=NewX; Y:=NewY;
Forml.Canvas.Pixels [X,Y]:= clBlack; end;
Procedure TCircle.Draw(NewX, NewY: integer); begin
Forml.Canvas.Brush.Color := clBtnFace; Forml.Canvas.Pen.Color := clBtnFace;
Forml.Canvas.Ellipse (X-Radius, Y-Radius, X+Radius, Y+Radius); X:=NewX; Y:=NewY;
Forml.Canvas.Pen.Color := clBlack; Forml.Canvas .Brush.Color := clBlack;
Forml.Canvas.Ellipse (X-Radius, Y-Radius, X+Radius, Y+Radius); end;
Для создания объектов вызовем два конструктора. OnePoint := TPoint.Create (10,10); OneCircle := TCircle.Create (100,100,20);
В результате получим два объекта, каждый из которых имеет сво" собственный метод для рисования себя (рис. 3.2 ). OnePoint OneCircle
Рис. 3.2. Значение полей и состояние статического метода объектов OnePoint и
OneCircle после создания Различие между статическими, динамическими и виртуальными методам наступает при попытке присвоить родительскому объекту дочерний объект. Правила контроля соответствия типов (typecasting) языка Object Pascal гласят, что объекту, как указателю на экземпляр объектного типа может быть присвоен адрес любого экземпляра любого из его Оочерних типов. Пусть далеевпрограмме выполняются операторы:
OnePoint OneCircle; OnePeint.Draw (100,100);
В этом случае после выполнения оператора присваивания значения полей X и 1 объектаOnePoint изменятся, а ссылка на код для методаDraw останется прежней (рис. 3.3). OnePoint OneCircle
Рис. 3.3. Значение полей и состояние статического метода объектов OnePoint и OneCircle после присваивания дочернего объекта родительскому объекту
Вызов методаOnePoint.Draw приведет к тому, что будет нарисована точка с новыми координатами (100,100). Иначе поведет себя объектOnePoint, если описать его методDraw как виртуальный или динамический, так как в этом случае начнет действовать механизм ООП полиморфизм.
§3.8. Виртуальные, динамические и абстрактные методы. Полиморфизм. Информация о типах времени выполнения (RTTI)
При определении метода в классе он может быть описан как виртуальный или динамический. Для этого после описания метода в родительском классе указывается специальная директива: соответственноvirtual илиdynamic.Если метод переопределяется в потомке, то для того, чтобы начал работать механизм полиморфизма после описания метода необходимо указать директиву override. Попытка перекрытия с директивой неoverride, avirtual или dynamic приведет не к перекрытию, а к созданию нового одноименного метода и полиморфизм не будет работать. Попытка применить директивуoverride при перекрытии статического метода вызовет ошибку компиляции. Пример 3.8. Использование виртуальных методов в полиморфизме, interface type
TPoint = class X, X : Integer;
constructor Create (a, b: integer); procedure Draw (Newx, Newy : integer); virtual; end;
TCircle = class (TPoint) Radius : Integer;
Constructor Create (a, b, R: integer); procedure Draw (NewX, NewX : integer); override; end; var
OnePoint : TPoint; OneCircle : TCircle;
Если в каком-либо базовом типе метод был объявлен как виртуальный, то он остается виртуальным во всех классах-наследниках (в частности, и в наследниках наследников). То есть у наследников классаTCircle методDraw также может быть перекрыт с использованием директивыoverride.
Отличие виртуальных методов от ранее рассмотренных статических метопов пр„ся после выполнения присваивания родительскомуобъекту дочернего
OnePoint := OneCirele;
Если теперь вызвать методOnePoint.Draw, то нарисуется окружность. Таким образом, объектOnePoint в результате произведенного присваивания изменил не только свои данные, но и свое поведение и стал вести себя не так как объекты своего класса, а так как объект дочернего класса.
Если описать другой класс, дочернийTPoint, и его объект:
Type
TSquare = class (TPoint) Edge : Integer;
constructor Create (a, b, E: integer);
procedure Draw (NewX, New! : integer); override; end; var
OneSquare : TSquare;
ii выполнить присваивание OnePoint := OneSquare;
ro объектOnePoint станет вести себя как объект классаTSquare, вызов метода OnePoint. Draw нарисует квадрат. Таким образом, один и тот же родительский объект ведет себя по-разному (полиморфно), в зависимости от того какой дочерний объект был ему присвоен. Точнее, не весь объект, а виртуальные методы объектаOnePoint ведут себя иначе.
Для чего используется полиморфизм? Допустим, средствами ООП описывается совокупность явлений или процессов. Сначала необходимо выделить их общие черты поведения, при этом важно даже не каким образом делается что- иибо, а то, что, по сути, выполняется одно и то же действие. Все эти схожие черты описываются в классе-родителе. Те из них, которые не изменяют своего содержания при переходе от общего к частному, реализуются в виде статических методов, те же, которые изменяются - как виртуальные или динамические методы, чтобы потом было возможно их перекрыть в классах-потомках.
Теперь для этой иерархии можно написать некий алгоритм, который выполняет некоторое общее для всех объектов действие. В качестве конкретного объекта, выполняющего это действие нужно описать объект класса-родителя, lie л и теперь присвоить этому объекту какой-либо из его потомков, то алгоритм будет делать одно и то же, по сути, действие, но по-разному. Таким образом, один и тот же алгоритм сможет правильно обрабатывать разные виды объектов! Использование таких возможностей ООП может значительно упростить алгоритм программы в целом.
Прим. яительно к нашему примеру полиморфизм заключается в том, что все геометрические фигуры - наследники класса «точка» умеют себя нарисовать, но псе делают это по-разному. Например, если имеется базовый класс графических объектовTPoint и ряд наследующих ему объектов геометрических фигур и в каждом из этих классов определен свой виртуальный методDraw, рисующий эту фигуру, то можно написать в программе: var GraphArray : array [1..10] of TPoint;
for i := 0 to 10 do GraphArray[i].Draw;
Если в массивGraphArray будут загружены объекты разных типов- маследниковTPoint, то несмотря на то, что обращение для каждого из них происходит как к объекту родительского класса, вызываться будет виртуальный методDraw именно этого объекта.
Различие между виртуальными и динамическими методами
В случае динамических и виртуальных методов у компилятора нет иозможности на этапе компиляции и компоновки жестко связать имя метода с адресом оперативной памяти, где находится его исполняемый код. Нужен механизм, позволяющий определить это прямо во время выполнения. Этот механизм называется поздним связыванием, он реализуется или через таблицу пнртуальньр: методов (Virtual Method Table - VMT) или "через таблицу динамических методов (Dynamic Method Table - DMT). В соответствии с этими
механизмами в Delphi имеется два варианта реализации позднего связывания виртуальные и динамические методы. Синтаксис у обеих этих директив один и тот же, результат - тоже. Различие заключается во внутреннем механизме, используемом компилятором для организации позднего связывания.
Виртуальные методы основаны на таблице виртуальных методов (VMT), которая представляет собой массив адресов всех виртуальных методов каждого. Когда компилятор встречает обращение к виртуальному методу объекта, он подставляет код, который обращается к VMT класса этого объекта и извлекает оттуда нужный адрес. Такая таблица заводится для каждого класса. В ней хранятся адреса всех виртуальных методов класса, независимо от того, были они перекрыты или нет в данном классе. В конечном счете, это приводит к эффекту распространения адресов таблицы виртуальных методов через всю иерархию классов (даже для методов, которые не переопределялись). Отсюда вытекают достоинства и недостатки виртуальных методов: они вызываются сравнительно быстро, однако для хранения указателей на них в таблицах VMT требуется большое количество памяти.
Вызовы динамических методов производятся при помощи уникального номера, определяющего метод. В таблице динамических методов класса хранятся индексы и адреса только тех динамических методов, которые описаны в данном классе. При вызове динамического метода происходит поиск в этой таблице; в случае неудачи просматриваются таблицы DMT всех классов-предков в порядке иерархии и, наконец, TObject, где имеется стандартный обработчик вызова динамических методов. Таким образом, поиск кода вызываемого метода в общем случае является более медленным, чем простой одношаговый вызов через VMT. Однако для больших и глубоких иерархий, использование динамических методов вместо виртуальных может привести к значительной экономии памяти. Пример 3.9. Описание двух классов с динамическими и виртуальными методами
Interface type
TClassl = class X, У : Integer; procedure StatMethod; procedure VirtualMethodl; virtual; procedure VirtualMethod2; virtual; procedure DynamicMethodl; dynamic; procedure DynamicMethodZ; dynamic; end;
TClass2 = class (TClassl) procedure StatMethod; procedure VirtualMethodl; override; procedure DynamicMethodl; override; end; var