Мы уже говорили о процедурном типе. Переменная процедурного типа – это указатель на адрес процедуры или функции. Если же мы хотим сделать ссылку не просто на процедуру или функцию, а на метод объекта, то мы должны добавить ключевое словосочетание of object, записанное после прототипа процедуры или функции:
type TMethod = procedure of object; TNotifyEvent = procedure(Sender: TObject) of object; |
Переменная такого типа называется указателем на метод (method pointer). Она занимает в памяти 8 байт и хранит одновременно ссылку на объект и адрес его метода (получается, на самом деле 2 указателя). Например:
type TNotifyEvent = procedure(Sender: TObject) of object; TMainForm = class(TForm) procedure ButtonClick(Sender: TObject);... end;var MainForm: TMainForm; OnClick: TNotifyEvent |
Тогда можно сделать следующую привязку:
OnClick:= MainForm.ButtonClick; |
Используя переменные типа указателя на метод можно описывать события. Рассмотрим пример с использованием ранее описанного класса TFigure.
Type TFigure = class private FOnDraw: TNotifyEvent; public property OnDraw: TNotifyEvent read FOnDraw write FOnDraw; end; |
и в методе Draw:
procedure TFigure.Draw;begin... //рисуем фигурку if Assigned(FOnDraw) then FOnDraw(Self); //уведомление о рисовании фигурки end;end; |
Обратите внимание, что вызов метода через указатель происходит лишь в том случае, если указатель не равен nil. Эта проверка выполняется с помощью стандартной функции Assigned, которая возвращает True, если ее аргумент является связанным указателем.
Описанный выше механизм называется делегированием, поскольку он позволяет передать часть работы другому объекту, например, сосредоточить в одном объекте обработку событий, возникающих в других объектах. Это избавляет программиста от необходимости порождать многочисленные классы-наследники и перекрывать в них виртуальные методы. Делегирование широко применяется в среде Delphi. Например, все компоненты делегируют обработку своих событий той форме, в которую они помещены.
|
Метаклассы
Ссылки на классы
Язык Delphi позволяет рассматривать классы объектов как своего рода объекты, которыми можно манипулировать в программе. Такая возможность рождает новое понятие — класс класса; его принято обозначать термином метакласс.
Для поддержки метаклассов введен специальный тип данных — ссылка на класс (class reference). Он описывается с помощью словосочетания class of, например:
type TTextReaderClass = class of TTextReader; |
Переменная типа TTextReaderClass объявляется в программе обычным образом:
var ClassRef: TTextReaderClass; |
Значениями переменной ClassRef могут быть класс TTextReader и все порожденные от него классы. Допустимы следующие операторы:
ClassRef:= TTextReader;ClassRef:= TDelimitedReader;ClassRef:= TFixedReader; |
По аналогии с тем, как для всех классов существует общий предок TObject, у ссылок на классы существует базовый тип TClass, определенный, как:
type TClass = class of TObject; |
Переменная типа TClass может ссылаться на любой класс.
Практическая ценность ссылок на классы состоит в возможности создавать программные модули, работающие с любыми классами объектов, даже теми, которые еще не разработаны.
Физический смысл и взаимосвязь таких понятий, как переменная-объект, экземпляр объекта в памяти, переменная-класс и экземпляр класса в памяти поясняет рисунок 3.4.
Рисунок 3.4. Переменная-объект, экземпляр объекта в памяти, переменная-класс и экземпляр класса в памяти
Методы классов
Метаклассы привели к возникновению нового типа методов — методов класса. Метод класса оперирует не экземпляром объекта, а непосредственно классом. Он объявляется как обычный метод, но перед словом procedure или function записывается зарезервированное слово class, например:
|
type TTextReader = class... class function GetClassName: string; end; |
Передаваемый в метод класса неявный параметр Self содержит не ссылку на объект, а ссылку на класс, поэтому в теле метода нельзя обращаться к полям, методам и свойствам объекта. Зато можно вызывать другие методы класса, например:
class function TTextReader.GetClassName: string;begin Result:= ClassName;end; |
Метод ClassName объявлен в классе TObject и возвращает имя класса, к которому применяется. Очевидно, что надуманный метод GetClassName просто дублирует эту функциональность для класса TTextReader и всех его наследников.
Методы класса применимы и к классам, и к объектам. В обоих случаях в параметре Self передается ссылка на класс объекта. Пример:
var Reader: TTextReader; S: string;begin // Вызов метода с помощью ссылки на класс S:= TTextReader.GetClassName; // S получит значение 'TTextReader' // Создание объекта класса TDelimitedReader Reader:= TDelimitedReader.Create('MyData.del'); // Вызов метода с помощью ссылки на объект S:= Reader.GetClassName; // S получит значение 'TDelimitedReader'end. |
Методы классов могут быть виртуальными. Например, в классе TObject определен виртуальный метод класса NewInstance. Он служит для распределения памяти под объект и автоматически вызывается конструктором. Его можно перекрыть в своем классе, чтобы обеспечить нестандартный способ выделения памяти для экземпляров. Метод NewInstance должен перекрываться вместе с другим методом FreeInstance, который автоматически вызывается из деструктора и служит для освобождения памяти. Добавим, что размер памяти, требуемый для экземпляра, можно узнать вызовом предопределенного метода класса InstanceSize.