Мегаобучалка Главная | О нас | Обратная связь


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



2020-02-03 205 Обсуждений (0)
Полиморфизм. Виртуальные и динамические методы 0.00 из 5.00 0 оценок




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

type
tFiled = class
function GetData:string; virtual; abctract;
end;

tStringFiled = class(tFiled)
fData:string;
function GetData: string; override;
end;

tIntegerFiled = class(tFiled)
fData:Integer;
function GetData: string; override;
end;

tExtendedFiled = class(tFiled)
fData:Extended;
function GetData: string; override;
end;

function tStringFiled.GetData: string;
Begin
Result:=fData;
End;

function tIntegerFiled.GetData: string;
Begin
Result:=IntToStr(fData);
End;

function tExtendedFiled.GetData: string;
Begin
Result:=FloatToStr(fData,ffFixed, 7, 2);
End;

function ShowData(aFiled:tFiled): string;
Begin
Form1.Label1.Caption:=aFiled.GetData;
End;

В этом примере классы содержат разнотипные поля данных fData, а также имеют унаследованный от tFiled виртуальный метод GetData, возвращающий данные в виде строки. Внешняя по отношению к ним процедура ShowData получает объект в виде параметра и показывает эту строку.

Согласно правилам контроля соответствия типов (typecasting) ObjectPascal, объекту, как указателю на экземпляр класса, может быть присвоен адрес экземпляра любого из дочерних типов. Это означает, что в предыдущем примере в процедуру ShowData можно передавать объекты классов tStringFiled, tIntegerFiled, tExtendedFiled и любого другого потомка tFiled.

Но какой (точнее, чей) метод GetData будет при этом вызван? Тот, который соответствует классу фактически переданного объекта. Этот принцип называется полиморфизмом.

Возвращаясь к рассмотренному выше примеру, отметим, что у компилятора нет возможности определить класс объекта, фактически переданного в процедуру ShowData на этапе компиляции. Механизм, позволяющий определить этот класс прямо во время выполнения называется поздним связыванием. Естественно, такой механизм должен быть связан с передаваемым объектом. Для этого служит таблица виртуальных методов (Virtual Method Table, VMT) и таблица динамических методов (Dynamic Method Table, DMT).

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

Динамические методы вызываются медленнее, но позволяют более экономно расходовать память. Каждому динамическому методу системой присваивается уникальный индекс. В таблице динамических методов класса хранятся индексы только тех методов только тех динамических методов, которые описаны в данном классе. При вызове динамического метода происходит поиск в этой таблице. В случае неудачи просматриваются DMT всех классов-предков в порядке их иерархии и, наконец, tObject, где имеется стандартный обработчик вызова динамических методов. Экономия памяти очевидна.

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

type
tParentClass=class
fFirstFiled:Integer;
fSecondFiled:longInt;
procedure StaticMethod;
procedure VirtualMethod1; virtual;
procedure VirtualMethod2; virtual;
procedure DynamicMethod1; dynamic;
procedure DynamicMethod2; dynamic;
end;

tChildClass=class(tParentClass)
procedure StaticMethod;
procedure VirtualMethod1; override;
procedure DynamicMethod1; override;
end;

Первый метод класса tChildClass создается заново, два остальных перекрываются. Создадим объекты этих классов:

var Obj1: tParentClass;
Obj2: tChildClass;

Внутренняя структура этих объектов показана ниже.

Первое поле каждого экземпляра каждого объекта содержит указатель на его класс. Класс, как структура состоит из двух частей. Начиная с адреса, на который ссылается указатель на класс, располагается таблица виртуальных методов. Она содержит адреса всех виртуальных методов класса, включая и унаследованные от предков. Перед таблицей виртуальных методов расположена специальная структура, содержащая дополнительную информацию. В ней содержатся данные, полностью характеризующие класс: имя, размер экземпляров, указатели на класс-предок и т.д. Одно из полей структуры содержит адрес таблицы динамических методов класса (DMT). Таблица имеет следующий формат: в начале - слово, содержащее количество элементов таблицы. Затем - слова, соответствующие индексам методов. Нумерация индексов начинается с –1 и идет по убывающей. После индексов идут собственно адреса динамических методов. Следует обратить внимание на то, что DMT объекта Obj1 состоит из двух элементов, Obj2 - из одного, соответствующего перекрытому методу DynamicMethod1. В случае вызова Obj2.DynamicMethod2 индекс не будет найден в DMT Obj2, и произойдет обращение к DMT Obj1. Именно так экономится память при использовании динамических методов.

Как указывалось выше, указатель на класс указывает на первый виртуальный метод. Служебные данные размещаются перед таблицей виртуальных методов, то есть с отрицательным смещением. Эти смещения описаны в модуле SYSTEM.PAS:

vmtSelfPtr = -76
vmtIntfTable = -72
vmtAutoTable = -68
vmtInitTable = -64
vmtTypeInfo = -60
vmtFiledTable = -56
vmtMethodTable = -52
vmtDynamicTable = -48
vmtClassName = -44
vmtInstanceSize = -40
vmtParent = -36
vmtSafeCallException = -32
vmtAfterConstruction = -28
vmtBeforeDestruction = -24
vmtDispatch = -20
vmtDefaultHandler = -16
vmtNewInstance = -12
vmtFreeInstance = -8
vmtDestroy = -4

Поля vmtDynamicTable, vmtDispatch и vmtDefaultHandler отвечают за вызов динамических методов. Поля vmtNewInstance, vmtFreeInstance и vmtDestroy содержат адреса методов создания и уничтожения экземпляров класса. Поля vmtIntfTable, vmtAutoTable, vmtSafeCallException введены для обеспечения совместимости с моделью объекта COM. Остальные поля доступны через методы объекта tObject. В Object Pascal эта информация играет важную роль и может использоваться программистом неявно. В языке определены два оператора - is и as, неявно обращающиеся к ней. Оператор is предназначен для проверки совместимости по присвоению экземпляра объекта с заданным классом. Выражение вида:

AnObject is tObjectType

Принимает значение True только если объект AnObject совместим по присвоению с классом tObjectType, то есть является объектом этого класса или его потомком.

Оператор as введен в язык специально для приведения объектных типов. С его помощью можно рассматривать экземпляр объекта как принадлежащий к другому совместимому типу:

with AObjectOfSomeType as tAnotherType do . . .

От стандартного способа приведения типов использование оператора as отличается наличием проверки на совместимость типов во время выполнения: попытка приведения к несовместимому типу приводит к возникновению исключительной ситуации eInvalidCast. После выполнения оператора as сам объект остается неизменным, но выполняются те его методы, которые соответствуют присваиваемому классу.

Вся информация, описывающая класс, создается и размещается в памяти на этапе компиляции. Доступ к информации вне методов этого класса можно получить, описав соответствующий указатель, называющийся указателем на класс или указателем на объектный тип (class reference). Он описывается при помощи зарезервированных слов class of. Например, указатель на класс tObject описан в модуле SYSTEM.PAS и называется tClass. Аналогичные указатели определены и для других важнейших классов: tComponentClass, tControlClass и т.д.

С указателем на класс тесно связано понятие методов класса. Такие методы можно вызывать и без создания экземпляра объекта - с указанием имени класса в котором они описаны. Перед описанием метода класса нужно поставить ключевое слово class.

Разумеется, методы класса не могут использовать значения, содержащиеся в полях класса: ведь экземпляра класса не существует!!! Методы класса служат для извлечения внутренней информации класса. Ниже перечислены методы класса tObject:

Метод и Описание

сlass function ClassName:ShortString
Возвращает имя класса

сlass function ClassNameIs(const Name:ShortString):Boolean
Принимает значение True, если имя класса равно заданному

сlass function ClassParent:tClass
Возвращает указатель на родительский класс

сlass function ClassInfo:pointer
Возвращает указатель на структуру с дополнительными данными об опубликованных методах и свойствах.

сlass function InstanceSize:Longint
Возвращает размер экземпляра класса

сlass function InheritsFrom (aClass: tClass):Boolean
Возвращает True, если данный класс наследует от заданного

сlass function MethodAddress(const Name:ShortString):Pointer
Возвращает адрес метода по его имени (только для опубликованных методов)

сlass function MethodName (Addres: pointer):ShortString
Возвращает имя метода по его адресу (только для опубликованных методов)

В Delphi 4 в класс tObject добавлены еще два виртуальных метода - AfterConstruction и BeforeDestruction. Как следует из названия, они вызываются сразу после создания экземпляра объекта и непосредственно перед уничтожением.

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

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

Type
tFirstClass=class
E:extended;
procedure SetData(aValue: Extended); overload;
end;

tSecondClass=class(tFirstClass)
I:integer;
procedure SetData(aValue: Integer); overload;
end;

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

Для перегрузки виртуального метода используется зарезервированное слово reintroduce:

procedure SetData(aValue:string); reintrouce;overload;

На перегрузку методов накладывается ограничение: нельзя перегружать методы, находящиеся в области видимости published.

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

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

Procedure NeverCallMe; virtual; abstract;

Никакого кода абстрактный метод не содержит. Его вызов приведет к созданию исключительной ситуации eAbstractError.

События

Операционная система Windows® основана на сообщениях. Сообщения эти возникают в результате действий пользователя, аппаратуры компьютера или других программ. Таких сообщений в Windows сотни, и по большому счету, написать программу для Windows - значит определить реакцию на некоторые из них.

Работать с таким количеством сообщений, даже имея под рукой справочник, нелегко. Поэтому одним из главных преимуществ Delphi является то, что программист полностью избавлен от необходимости работать с сообщениями Windows (хотя такая возможность у него имеется). Типовых событий в Delphi - не более двух десятков, и все они имеют простую интерпретацию, не требующую глубоких знаний среды.

С точки зрения языка, событие - это поле процедурного типа, предназначенное для создания пользовательской реакции на те или иные входные воздействия:

Property OnMyEvent: tMyEvent read FMyEvent write FMyEvent;

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

Внутри библиотеки времени выполнения Delphi вызовы обработчиков событий находятся внутри методов, обрабатывающих сообщения Windows. Выполнив необходимые действия, этот метод проверяет, известен ли адрес обработчика, и, если это так, вызывает его.

События имеют разное количество и тип параметров, в зависимости от происхождения и назначения. Общим для всех события является параметр Sender - он указывает на объект, вызвавший событие. Самое простое событие - tNotifyEvent - не имеет других параметров.

tNotifyEvent = procedure(Sender:tObject) of object;

Тип метода, предназначенный для извещения о нажатии клавиши, предусматривает передачу в процедуру кода этой клавиши, о передвижении мыши - ее текущих координат и т.д.

Имя события в Delphi начинается с префикса On: OnClick, OnCreate, OnMouseDown и т.д. Имена методов - обработчиков событий состоят из имени объекта, генерирующего событие, и корня имени события: OkButtonClick. Дважды щелкнув мышью в инспекторе объектов на странице Events в поле напротив любого события, вы получите скелетный код (заготовку) этого события.

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

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

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

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

If Sender is tMenuItem then ...

Если же все объекты, разделяющее между собой один обработчик события, относятся к одному классу, можно использовать свойство tObject.Tag.

Ниже приведены названия некоторых, наиболее употребительных событий Delphi:

Событие - Тип обработчика - Когда возникает

OnClick - TNotifyEvent - При нажатии левой клавиши мыши

OnActivate - TnotifyEvent - После передачи объекту фокуса

OnCreate - TnotifyEvent - После создание объекта

OnDestroy - TnotifyEvent - Перед уничтожением объекта

OnDeactivate - TnotifyEvent - Перед уходом фокуса из объекта

OnKeyPress - TkeyPressEvent - При нажатии клавиши

OnMouseDown - TmouseEvent - При нажатии клавиши мыши

OnMouseMove - TmouseMoveEvent - При движении мыши над объектом

OnMouseUp - TmouseEvent - При отпускании клавиши мыши

а также их типы:

Тип
Описание

TnotifyEvent
type TnotifyEvent = procedure (Sender: TObject) of object;

TkeyPressEvent
type TkeyPressEvent = procedure (Sender: TObject; var Key: Char) of object;

TmouseEvent
TmouseEvent = procedure (Sender: TObject; Button: TmouseButton; Shift: TShiftState; X, Y: Integer) of object;

TmouseMoveEvent
TmouseMoveEvent = procedure(Sender: TObject; Shift: TShiftState; X, Y: Integer) of object;

Стандартные события описаны в модуле Classes.

Динамические массивы

В Delphi существуют динамические массивы, то есть массивы, длину которых можно изменять во время выполнения программы. аньше проблема динамических массивов стояла довольно остро. Стандартных средств для работы с ними не было, и программистам приходилось вручную создавать подобные структуры (как правило, на основе динамических переменных). Теперь компилятор Delphi сам выполняет всю “грязную” работу. Параллельно с динамическими, в Delphi сохранились и обычные, статические массивы.

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

var A1:array of real,

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

procedure SetLength(var A; NewLength: Integer);

Здесь A - указатель на динамический массив, NewLength - длина массива.

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

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

Var A1,A2:array[0..4] of real;
A1:=A2;

В случае со статическими массивами, означает, что всем элементам массива A1 присваиваются значения соответствующих элементов массива A2, но при этом, переменные A1и A2 - разные переменные, хранящиеся в разных областях памяти.

В случае с динамическими массивами присвоение их друг другу - не более чем присвоение указателей. ассмотрим пример:

Var A1,A2:array of real;
. . .
SetLength(A1,5);
A1[0]:=0.0;
A2:=A1;
A2[0]:=2.0;

Значение A2[0] также изменится и станет равно 2. Это произошло потому, что после присвоения указатели ссылаются на один и тот же адрес в памяти и изменения в одном массиве приведут к синхронным изменениям в другом.

Для того, чтобы действительно скопировать элементы в A2, необходимо использовать функцию Copy:

function Copy(A; Index, Count: Integer): string;

Здесь А - указатель на копируемый массив,

Index - индекс первого копируемого элемента,

Count - число копируемых элементов.

A2:=Copy(A1,0,Length(A1));

Освободить выделенную под массив память можно при помощи процедуры finalize:

procedure Finalize( var A [; Count: Integer] );

здесь V - указатель на динамический массив, Count - количество массивов (в случае, когда несколько динамически создаваемых структур размещаются подряд в одном непрерывном блоке памяти, их можно освободить одним вызовом Finalize); или просто присвоив указателю на массив значение nil. В этом случае память не будет потеряна (как могло бы показаться на первый взгляд), а будет вызвана соответствующая процедура, корректно освобождающая память. Причем, память освободиться только тогда, когда на нее не уже не ссылается ни одна динамически созданная структура.

Значения граничных индексов (как статических так и динамических) можно получить при помощи функций Low(A) - нижний граничный индекс и Hight(A) - верхний граничный индекс. Число элементов массива - функцией Length:

function Length(A): Integer;

Дальнейшим развитием идеи динамических массивов являются многомерные динамические массивы.

Var AA: array of arrray of real;
SetLength(AA,10,5);
AA[9,4]:=1;
AA[9][4]:=1;

Можно пойти еще дальше, и создать динамические массивы с переменной длиной по разным индексам. Для создания такого массива сначала нужно задать его размерность по первому индексу:

SetLength(AA,10);

Это означает, что массив будет состоять из десяти строк. Теперь длина каждой строки задается отдельно:

SetLength(AA[0],5);
SetLength(AA[1],6);
SetLength(AA[2],3);
SetLength(AA[3],8);
. . .

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

Перегружаемые функции

Перегрузка функций ничем не отличается от перегрузки методов. Основная идея перегрузки функций и методов заключается в том, что в программе можно иметь две или болеефункции с одинаковыми именами и разными наборами параметров. Перегружаемые функции, также как и методы описываются с ключевым словом overload. Например:

function OverloadDemo(val : Byte) : string; overload;
function OverloadDemo(val : Char) : string; overload;

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

При вызове перегруженной функции компилятор определяет тип фактически передаваемого параметра (в приведенном выше примере - Byte или Char) и в зависимости от этого подставит вызов соответствующего варианта функции.



2020-02-03 205 Обсуждений (0)
Полиморфизм. Виртуальные и динамические методы 0.00 из 5.00 0 оценок









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

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:
Как построить свою речь (словесное оформление): При подготовке публичного выступления перед оратором возникает вопрос, как лучше словесно оформить свою...
Организация как механизм и форма жизни коллектива: Организация не сможет достичь поставленных целей без соответствующей внутренней...
Личность ребенка как объект и субъект в образовательной технологии: В настоящее время в России идет становление новой системы образования, ориентированного на вхождение...



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (205)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.008 сек.)