Interface

Begin

Begin

Begin

Type

Begin

.....

Begin

T2.SetData (1); (2)

T2.SetData (1.1); (1)

Begin

.....

.....

.....

Begin

.....

Begin

Begin

End.

Begin

Статические методы.

По умолчанию методы класса являются статическими – их адрес определяется во время компиляции программы (раннее связывание), поэтому они вызываются быстрее всего.

В одноименных статических методах порожденных классов списки параметров могут отличаться.

Со статическим методом связан следующий способ наследования: если в описании потомка появляется метод с тем же именем, что и у предка, то в этом типе и во всех его потомках этот метод будет перекрыт (замещен, переопределен).

При обращении к статическому методу компилятор точно знает класс, которому данный метод принадлежит. Описанный тип класса (во время компиляции) или объектная переменная, используемая в вызове статического метода, определяет, которое выполнение метода активировать.

Например:

type TFigure=class

procedure draw;

end;

TRectangle=class (TFigure)

procedure draw; {эта процедура будет перекрыта

предыдущей}

end;

… … … …

var Figure:TFigure;

Rectangle:TRectangle;

Figure:=TFigure.create ;{конструктор create уноследова

у предка}

Figure.draw;

{ вызывается метод draw класса TFigure }

Figure.free; {уничтожаем объект Figure}

Figure:=TRectangle.create; {Figure-новая, отличная от

предыдущей}

Figure.draw;

{ вызывается метод draw класса TRectangle }

Figure.free;

Rectangle:=TRectangle.create; {описываем новую

переменную}

Rectangle.draw;

{ вызывается метод draw класса TRectangle }

Rectangle.free;

Здесь разные методы с именами draw вызываются в разных классах. Их адрес определяется при компиляции, поэтому статические методы вызываются быстрее всех.

Полиморфизм. Виртуальные и динамические методы.

Принципиально отличаются от статических виртуальные и динамические методы.

В Delphi чаще используется динамическое замещение методов на этапе прогона программы. Для реализации этого метод, замещаемый в родительском классе должен объявляться как динамический (dynamic) или виртуальный (virtual). Встретив такое объявление компилятор создает две таблицы:

DMT – Dynamic Method Table

VMT – Virtual Method Table

Далее компилятор поместит в них адреса точек входа соответственно динамических и виртуальных методов. При каждом обращении к замещаемому методу компилятор вставляет код, позволяющий извлечь адрес точки входа в подпрограмму из той или другой таблицы. А в классе – потомке замещающий метод объявляется с директивой Override (перекрыть). Получив это указание, компилятор создаст код, который при выполнении программы поместит в родительскую таблицу точку входа метода класса – потомка, что позволит родителю выполнить нужные действия с помощью нового метода.

Пример1. Пусть существует некоторый обобщенный класс для хранения данных и два его потомка для хранения строк и целых чисел:

Пример 1:

Type TField=class {класс родитель}

function GetData: string; virtual; abstract;

end;

TStringField=class(TField) {класс потомок}

FData:string;{поле}

Function GetData: string; override;{метод

перекрыт}

end;

TIntegerField=class(TField){класс потомок}

FData:integer;{поле}

function GetData: string; override;{перекрыт}

end;

.....

function TStringField.GetData;{описывается метод

GetData класса TStringField}

Result:=FData;

end;

function TIntegerField.GetData; {описывается

метод GetData класса TIntegerField}

Result:=IntToStr(FData);

end;

Procedure Show (F:TField);

Form1.Label1.Caption:= F.GetData;

end;

В этой задаче классы TIntegerField и TStringField содержат разнотипные поля данных FData и единственное что они умеют – это сообщать о значении своих данных текстовой строкой при помощи метода GetData

В каждом классе сообщается о значении разнотипных полей. Внешняя процедура Show получает объект в виде параметра и показывает строку данных. (Form1.Label1.Caption:= F.GetData;) В процедуре Show параметр F описан как объект класса TField. Это значит, что в нее можно передавать объекты классов TStringField, TIntegerField как потомков класса TField.

Возникает вопрос: чей метод GetData при этом будет вызван? Ответ: тот, который соответствует классу фактически переданного объекта.


Т.о. правило соответствия типов языка Object Pascal гласит: объекту, как указателю на экземпляр объектного типа может быть присвоен адрес любого экземпляра любого из дочерних типов. Этот принцип называется полиморфизмом. Полиморфизм – наиболее важный принцип ООП.

В нашем примере программисту, пишущему процедуру Show важно лишь, что любой объект, переданный в нее, является потомком класса TField.

Если такую процедуру скомпилировать и поместить в динамическую библиотеку, то эту библиотеку можно будет раз и навсегда использовать без изменений, хотя будут появляться и новые, неизвестные в момент её создания классы-потомки TField.

Абстрактные методы.

В классе метод может быть объявлен абстрактным с помощью директивы abstract.

Абстрактными называются методы, которые определены в классе, но не содержат никаких действий, никогда не вызываются и обязательно должны быть переопределены в потомках класса. Абстрактными могут быть виртуальные и динамические методы.

В рассмотренном выше ПРИМЕРЕ 1 можно рассмотреть, для чего нужно использование абстрактных методов. Классы, содержащие абстрактные методы, называются абстрактными. Такие классы инкапсулируют общие свойства своих неабстрактных потомков, но объекты абстрактных классов никогда не создаютсяи не используются. Их абстрактные методы не описываются. Для эксплуатации абстрактных классов в библиотеку классов Delphi включаются классы – потомки, в которых перекрываются абстрактные методы родителя. Таким способом абстрактные классы и абстрактные методы инкапсулируют доступ к методам потомков.

В ПРИМЕРЕ 1 класс TField не используется сам по себе, его абстрактный метод GetData не описывается (не имеет кода). Основное предназначение класса TField – быть родоначальником иерархии конкретных классов-потомков и дать возможность абстрагироваться от частности. Для описания абстрактных методов служит директива abstract, которая записывается после директивы virtual или dynamic.

Таблицы виртуальных и динамических методов – VMT и DMT.

Рассмотрим снова ПРИМЕР 1. Ясно, что у компилятора нет возможности определить класс объекта, фактически переданного в процедуру Show. Нужен механизм для определения класса объекта фактически переданного в процедуру Show, который позволяет определить это прямо во время выполнения программы. Это называется поздним связыванием. В качестве такого механизма служат таблицы VMT и DMT.

Когда компилятор встречает обращение к виртуальному методу, он подставляет вместо прямого вызова по конкретному адресу код, который обращается к VMT и извлекает оттуда нужный адрес. Таблица VMT есть у каждого класса. В ней хранятся адреса всех виртуальных методов класса: и унаследованных от предков, и перекрытых в данном классе.

Отсюда достоинства и недостатки виртуальных методов. Они вызываются сравнительно быстро, однако, для хранения указателей на них в таблице VMT требуется большой объем памяти.

Разница между DMT и VMT механизмами состоит в том, что таблица DMT содержит адреса только тех методов, которые объявлены и перекрыты как dynamic в данном классе. В то время как VMT содержит адреса виртуальных методов не только данного класса, но и всех его предков, независимо от того, унаследованы ли они от предков или перекрыты в данном классе.

Значит бьльшая по размеру таблица VMT обеспечит более быстрый поиск. В то время как при обращении к динамическому методу программа сначала рассмотрит DMT у объекта, а затем, в случае неудачи, у всех классов – предков в порядке иерархии, пока не найдет нужную точку входа.

Итак, динамические методы вызываются медленнее виртуальных, но позволяют экономить память компьютера.

Перекрытие виртуальных и динамических методов.

Для перекрытия виртуальных и динамических методов служит новая директива override, с помощью которой и только с ней можно переопределить оба этих типа методов.

Пример 2:

Type TFirstClass=class

FMyField1:Integer;

FMyField2:LongInt;

Procedure StatMetod;

Procedure VirtMetod1; virtual;

Procedure VirtMetod2; virtual;

Procedure DynaMetod1; dynamic;

Procedure DynaMetod2; dynamic;

end;

TSecondClass=class(TFirstClass) {класс потомок}

Procedure StatMetod;

Procedure VirtMetod1; override;

{метод перекрыт}

Procedure DynaMetod1; override;

{метод перекрыт}

end;

Var Object1:TFirstClass;

Object2:TSecondClass;

Первый из методов StatMetod создается в новом классе заново {перекрывается статически}, остальные два перекрываются как виртуальный и динамический с помощью директивы override. Попытка применить директиву override к статическому методу вызовет ошибку компиляции.

Как устроен объект изнутри

Рассмотрим, как устроен объект изнутри и как происходит вызов методов в этом примере. Ясно, что каждый экземпляр класса (объект) содержит отдельную копию всех его полей. Ясно также, что внутри экземпляра есть указатели на таблицы VMT и DMT.

Нарисуем структуру объектов Object1 и Object2:

Object1

Указатель на объект Object1
 
Указатель на класс TFirstClass
Поле FMyField1
Поле FMyField2
Число динамических методов (2)
Индекс метода TFirstClass.DynaMetod1 (-1)
Индекс метода TFirstClass.DynaMetod2 (-2)
Адрес метода TFirstClass.DynaMetod1
Адрес метода TFirstClass.DynaMetod2
Информация класса TFirstClass
Адрес метода TFirstClass.VirtMethod1
Адрес метода TFirstClass.VirtMethod2

Object2

Указатель на объект Object2
 
Указатель на класс TSecondClass
Поле FMyField1
Поле FMyField2
Информация класса TSecondClass
Адрес метода TSecondClass.VirtMethod1
Адрес метода TSecondClass.VirtMethod2
Число динамических методов (1)
Индекс метода TSecondClass.DynaMetod1 (-1)
Адрес метода TSecondClass.DynaMetod1

Объект как экземпляр класса содержит указатель на его класс. Класс как структура состоит из двух частей. Начиная с адреса, на который ссылается указатель на класс, располагается таблица VMT. Она содержит адреса всех виртуальных методов класса, включая унаследованные от предков.

Отметим, что в объектах Object1 и Object2 длина таблиц VMT одинакова, в них по два элемента. Перед каждой VMT располагается специальная структура – информация класса. Одно из полей информации класса содержит адрес таблицы DMT класса. Таблица DMT имеет следующий формат: в начале – количество элементов таблицы, затем – индексы методов, нумерация которых начинается с (-1) и идет по убывающей. После индексов идут адреса динамических методов. Обратим внимание, что DMT объекта Object1 состоит из двух элементов, а объекта Object2 – из одного метода, соответствующего перекрытому методу DynaMetod1 в данном классе.

Внимание! В случае вызова Object2.DynaMetod2 индекс и адрес его не будут найдены в DMT Object2, тогда произойдет обращение к таблице DMT объекта Object1, в которой содержится адрес нужного метода DynaMetod2. В случае вызова Object2.VirtMetod2 адрес его сразу будет найден в таблице VMT объекта Object2.

Это демонстрирует экономию памяти при использовании динамических методов и экономию времени вызова для виртуальных методов.

Итоги:

Полиморфизм – это возможность описания одинакового по имени действия, применяемого ко всем объектам иерархии наследования, то есть возможность иметь несколько методов с одним и тем же именем для различных объектов одной иерархии. Это средство дает возможность развития объектов в потомках. Оно реализуется тем, что объект-потомок может добавлять и переопределять методы, то есть заменять методы предка на новые с теми же именами. Какой из методов будет выполняться при обращении к методу с заданным именем, определяется типом объекта (предок или потомок) и видом используемого метода (статический, виртуальный или динамический).

Полиморфизм – это свойство родственных классов решать схожие по смыслу проблемы разными способами.

В ООП действуют следующие правила описания и наследования методов:

1. Служебные слова virtual и dynamic вводят (описывают) новый виртуальный или динамический метод;

2. Для перекрытия наследуемого статического метода никаких дополнительных служебных слов не используется. Но если в порожденном классе надо перекрыть наследуемый виртуальный или динамический метод используется служебное слово override;

3. В одноименных статических методах порожденных классов списки параметров могут отличаться. Списки параметров виртуальных и динамических методов порождающих и порожденных классов должны быть идентичными.

Перезагрузка методов.

Четвертая группа методов – перезагружаемые (Overload). Заметим, что и виртуальные и динамические методы могут быть перезагружаемыми. Перезагрузка нужна, чтобы произвести одинаковые или похожие действия с разнотипными данными. Статический метод перекрытия приводит к тому, что потомок «не видит» перекрытый родительский метод и может обращаться к нему только с помощью слова inherited, поэтому в Delphi используется перезагрузка, с помощью которой становятся видны одноименные методы как родителя, так и потомка.

Пример:

Type TFirst=class

i:Extended;

procedure SetData(x:Extended);

end;

TSecond=class(TFirst)

j:Integer;

procedure SetData(AValue:Integer);

end;

var T2:TSecond;

В этом примере первый вызов метода SetData из объекта T2 с переменной =1.1 (не целая) вызовет ошибку! А второй вызов не приводит к ошибке, т.к. внутри объекта T2 статический метод с параметром типа Extended перекрыт одноименным методом с параметром типа Integer. Компилятор внутри T2 не признает параметр типа Extended.

Для доступа к методу SetData класса TFirst необходимо использовать служебное слово inherited. Например:

procedure TSecond.SetData;

x:=1.1;

inherited SetData (x);

j:=x;

end;

Но нам нужно произвести схожие действия (1), (2) с разнотипными данными в строках одной программы.

Может быть, метод сделать виртуальным?

Нет, нельзя! Поскольку тип и количество параметров в одном и том же виртуальном методе должны в точности совпадать.

Начиная с версии Delphi4 появилась возможность перезагрузить методы (overload), это позволяет видеть одноименные методы как родителя, так и потомка. Чтобы одноименные методы можно было отличить друг от друга, каждый из них должен иметь уникальный набор параметров. В ходе компиляции при обращении к одному из одноименных методов компилятор проверяет тип и количество параметров обращения и на основе этой проверки выбирает нужный метод.

Overload – директива, позволяющая перезагрузить методы:

Type TFirst=class

i:Extended;

procedure SetData(x:Extended); Overload;

end;

TSecond=class(TFirst)

j:Integer;

procedure SetData(AValue:Integer); Overload;

end;

var T2:TSecond;

.....

Теперь в программе можно использовать метод как родителя, так и потомка.

T2.SetData (1.1);

T2.SetData (1);

Можно перезагрузить и виртуальный, и динамический методы. В этом случае надо добавить директиву reintroduce перед директивой overload.


ЗАДАЧА С ИСПОЛЬЗОВАНИЕМ ПОЛИМОРФИЗМА

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

Пусть определены 3 класса, один из которых является базовым для двух других:

Tperson=class { базовыйкласс }

fname:string;

constructor Create(name:string);

function info:string; virtual;

end;

Tstud=class(Tperson) { класс– потомок }

fgr:integer; { поле для номера группы }

constructor Create(name:string;gr:integer);

function info:string; override;

end;

Tprof=class(Tperson) { класс– потомок }

fdep:string;; { поле для названия кафедры }

constructor Create(name:string; dep:string);

function info:string; override;

end;

В каждом из этих классов определен метод info. В базовом классе при помощи директивы virtual метод info объявлен виртуальным. Это дает возможность классу–потомку произвести замену виртуального метода своим собственным. В каждом классе–потомке определен свой метод info, который замещает соответствующий метод родительского класса и отмечается директивой override.

Определим метод info для каждого класса индивидуально:

function Tperson.info:string;

result:=’’;

end;

function Tstud.info:string;

result:=fname+' gruppa '+inttostr(fgr);

end;

function Tprof.info:string;

result:=fname+' department '+fdep;

end;

Далее в программе список всех людей можно представить массивом объектов класса Tperson. Отметим, что объект – указатель.

Список людей имеет вид:

list: array[1..szl] of Tperson; { szl – размер списка }

Объявить подобным образом список можно потому, что OP позволяет присвоить указателю на родительский класс значение указателя на класс– потомок. Поэтому элементами массива list могут быть как объекты класса Tstud, так и объекты класса Tprof.

Вывод списка можно осуществить применением метода info к элементам массива, например:

St:= ’’;

for i:=1 to szl do

if list[i]<>nil then

St:=St+list[i].info+#13;

ShowMessage('Spisok:'+#13+St); { вывод в окно сообщения}

Во время работы программы каждый элемент массива может содержать как объект типа Tstud, так и объект типа Tprof.

Концепция полиморфизма обеспечивает применение к объекту именно того метода info, который соответствует типу объекта.

Напишем программу, которая использует объявления классов Tperson, Tstud, Tprof, формирует список студентов и преподавателей и выводит полученный список в окно сообщения. Будем использовать визуальное программирование.

Окно формы будет иметь вид:


GroupBox1—это компонент, объединяющий группу компонентов, связанных по смыслу. В данном случае он включает 2 зависимых переключателя – RadioButton1 и RadioButton2

Текст модуля кода программы:

unit Polimorfizm;


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: