Виртуальные методы
Метод становится виртуальным, если за его объявлением в типе объекта стоит новое зарезервированное слово virtual. Помните, что если вы объявляете метод в родительском типе как virtual, то все методы с аналогичными именами в дочерних типах также должны объ- являться виртуальными во избежание ошибки компилятора.
Ниже приведены знакомые вам объекты из примера платежной ве- домости, должным образом виртуализированные:
tyрe PEmрloyee = ^TEmployee; TEmployee = object Name, Title: string[25]; Rate: Real; constructor Init (AName, ATitle: String; ARate: Real); function GetPayAmount : Real; virtual; function GetName : String; function GetTitle : String; function GetRate : Real; рrocedure Show; virtual; end;
PHourly = ^THourly; THourly = object(TEmployee); Time: Integer; constructor Init (AName, ATitle: String; ARate: Real; Time: Integer); function GetPayAmount : Real; virtual; function GetTime : Integer; end;
PSalaried = ^TSalaried; TSalaried = object(TEmployee); function GetPayAmount : Real; virtual; end;
PCommissioned = ^TCommissioned; TCommissioned = object(Salaried); Commission : Real; SalesAmount : Real; constructor Init (AName, ATitle: String; ARate, ACommission, ASalesAmount: Real); function GetPayAmount : Real; virtual; end;
А ниже приводится пример для насекомых, дополненный вирту- альными методами.
tyрe TWinged = object(Insect) constructor Init (AX, AY : Integer) рrocedure Show; virtual; рrocedure Hide; virtual; end;
tyрe TBee = object(TWinged) constructor Init (AX, AY : Integer) рrocedure Show; virtual; рrocedure Hide; virtual; end;
Прежде всего обратите внимание, что метод MoveTo, показанный для типа TBee, теперь удален из его определения. Теперь типу TBee больше нет нужды переопределять метод MoveTo типа TWinged с по- мощью немодифицируемой копии, компилируемой в его собственной об- ласти действия. Вместо этого, MoveTo теперь может наследоваться от TWinged со всеми вложенными в MoveTo вызовами, которые, одна- ко, будут вызывать методы из TBee, а не из TWinged, как это про- исходило в полностью статической иерархии объектов.
Отметьте также новое зарезервированное слово constructor (конструктор), заменившее зарезервированное слово рrocedure для TWinged.Init и TBee.Init. Конструктор является специальным типом процедуры, которая выполняет некоторую установочную работу для механизма виртуальных методов. Более того, конструктор должен вы- зываться перед вызовом любого виртуального метода. Вызов вирту- ального метода без предварительного вызова конструктора может привести к блокированию системы, а у компилятора нет способа про- верить порядок вызова методов.
Каждый тип объекта, имеющий виртуальные методы, обязан иметь конструктор.
Предупреждение: Конструктор должен вызываться перед вызовом любого другого виртуального метода. Вызов виртуального метода без предыдущего обращения к конструктору может вызвать блокировку системы, и компилятор не сможет проверить порядок, в котором вы- зываются методы.
Примечание: Для конструкторов объекта мы предлагает использовать идентификатор Init.
Каждый отдельный экземпляр объекта должен инициализироваться отдельным вызовом конструктора. Недостаточно инициализировать один экземпляр объекта и затем присваивать этот экземпляр другим. Другие экземпляры, даже если они могут содержать правильные дан- ные, не будут инициализированы оператором присваивания и заблоки- руют систему при любых вызовах их виртуальных методов. Например:
var FBee, GBee: Bee; { создать два экземпляра Bee } begin FBee.Init(5, 9) { вызов конструктора для FBee } GBee := FBee; { Gbee недопустим! } end; Что же именно создает конструктор? Каждый тип объекта содер- жит нечто, называемое таблицей виртуального метода (ТВМ) в сег- менте данных. ТВМ содержит размер типа объекта и для каждого вир- туального метода указатель на код, выполняющий данный метод. Конструктор устанавливает связь между вызывающей его реализацией объекта и ТВМ типа объекта.
Важно помнить, что имеется только одна ТВМ для каждого типа объекта. Отдельные экземпляры типа объекта (т.е. переменные этого типа) содержат только соединение с ТВМ, но не саму ТВМ. Конструк- тор устанавливает значение этого соединения в ТВМ. Именно благо- даря этому вы нигде не можете запустить выполнение перед вызовом конструктора.