Библиотека программиста

«Ни один ремесленник, который стремится к вершинам своей профессии, не примет негодных инструментов; и ни один производитель, который ценит качество работы, не будет упрашивать ремесленника принять их»

Главная страница > Язык Object Pascal > 30. Ссылки на класс

30. Ссылки на класс

В некоторых случаях необходимо выполнить операции над собственно классом, а не над экземплярами классов, т.е. объектами. Это случается, например, когда вызывается конструктор с указанием имени типа класса. Есть еще ряд ситуаций, в которых необходимо использовать ссылки на класс.

Типы ссылка на класс (Class-reference types).

Тип ссылка на класс, иногда называемый метаклассом (metaclass) описывается по такому формату:

class of classtype

Здесь classtype – имя типа класса. Если тип classtype1 является родительским для типа classtype2, то класс classtype2 совместим по присваиванию с классом classtype1. Отсюда следует, что описание

type TClass = class of TObject;

var AnyObj : TClass;

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

Для того чтобы понять, как можно использовать тип ссылка на класс, приведем описание конструктора класса TCollection из модуля Classes библиотеки VCL Delphi:

type TCollectionItemClass = class of TCollectionItem;

. . .

TCollection = class(TPersistent)

constructor Create(ItemClass: TCollectionItemClass);

. . .

Это описание означает, что для создания экземпляра класса TCollection, т.е. объекта, необходимо передать конструктору класса имя класса, имеющего тип TCollectionItem или являющегося производным от него.

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

Конструкторы и ссылки на класс (Constructors and class references).

Конструктор может быть вызван с использованием переменной типа ссылка на класс. Это позволяет конструировать объекты, тип которых неизвестен во время компиляции. Посмотрите на такой пример функции:

type TControlClass = class of TControl;

Function CreateControl(ControlClass: TControlClass; const ControlName: string;
X, Y, W, H: Integer): TControl;

Begin

Result := ControlClass.Create(MainForm);

with Result do

begin

Parent := MainForm;

Name := ControlName;

SetBounds(X, Y, W, H);

Visible := True;

end;

End;

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

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

var But : TControl;

But:=CreateControl(TButton,'RunButton',0,0,100,30);

(But as TButton).Caption:='RunTime-Button';

Можно поступить и таким образом:

var Cls : TControlClass;

// …

Cls:=TButton;

But:=CreateControl(Cls,'Run_Time_Button',200,100,100,50);

(But as Cls).Hint:='Кнопка, созданная на этапе выполнения';

(But as Cls).ShowHint:=true;

В этом примере в качестве первого параметра функции CreateControl используется ссылка на класс, а не имя класса непосредственно, что предоставляет программисту большие возможности.

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

Функцию CreateControl можно реализовать так, чтобы в ней не приходилось использовать непосредственно имя родительского компонента MainForm:

Function CreateControlEx(const ControlClass: TControlClass;

const ControlName: string;

const ParentClass : TObject;

X, Y, W, H: Integer): TControl;

Begin

Result := ControlClass.Create(ParentClass as TComponent);

with Result do

begin

Parent := ParentClass as TWinControl;

Name := ControlName;

SetBounds(X, Y, W, H);

Visible := True;

end;

End;

Вот примеры вызова функции CreateControlEx:

var But1 : TControl;

But1:=CreateControlEx(TButton,'RunButton',Form1,0,0,100,30);

// или

But1:=CreateControlEx(TButton,'RunButton',Self,0,0,100,30);

Операции класса is и as (Class operators).

Любой класс наследует от TObject методы (функции) ClassType и ClassParent, которые возвращают ссылку на класс объекта и на непосредственный родительский класс соответственно. Обе функции возвращают значение типа TClass (TClass = class of TObject), которое может быть приведено к более конкретному типу. Каждый класс наследует также метод InheritsFrom, который проверяет, действительно ли объект, вызвавший его, является производным от указанного класса. Эти методы используются операциями is и as, вследствие чего необходимость их прямого вызова возникает редко.

Операция is (is operator).

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

SomeObject is TSomeClass

имеет значение True, если объект SomeObject является экземпляром класса TSomeClass или какого-либо производного от него класса. В противном случае значение выражения – False. Если объект имеет значение nil, то выражение также имеет значение False.

Если объект SomeObject не имеет никакого отношения к классу SomeClass, т.е. они имеют разные типы и "не состоят в родстве", возникает ошибка этапа компиляции.

Приведем пример использования операции is:

if ActiveControl is TEdit then TEdit (ActiveControl). SelectAll;

Этот оператор вызывает метод SelectAll (выделить весь текст) объекта ActiveControl, но предварительно проверяет его принадлежность классу TEdit и выполняет приведение типа объекта к типу TEdit.

Операция as (as operator).

Эта операция введена в язык для приведения объектных типов. Значением выражения

SomeObject as TSomeclass

является ссылка на тот же объект SomeObject, но как имеющий тип класса TSomeClass. На этапе выполнения SomeObject должен быть экземпляром класса TSomeClass, производного от него класса или иметь значение nil. Если это не так, возникает ошибка (времени выполнения). Аналогично операции is, если объект SomeObject не имеет никакого отношения к классу TSomeClass, т.е. они имеют разные типы и "не состоят в родстве", возникает ошибка этапа компиляции. Пример использования операции:

with Sender as TButton do

begin

Caption := '&Ok'; OnClick := OkClick;

end;

При применении операции as часто приходится использовать скобки, например

(Sender as TButton).Caption := '&Ok';

Операция as часто используется в обработчиках событий. Для обеспечения совместимости источник события (Sender) в большинстве случаев описывается как имеющий тип TObject, а фактическим источником часто является форма или другие компоненты. Для того чтобы воспользоваться свойствами фактического источника и используется операция as

(Sender as TControl).Caption:='NewName';

От простого приведения типа TSomeClass(SomeObject) операция as отличается проверкой на совместимость типов. Приведем пример:

procedure TForm1.Button1Click(Sender: TObject);

begin

TEdit(Sender).Text:='Сюрприз!';

TEdit(Sender).Modified := true;

(Sender as TEdit).Text:='';

end;

Текст процедуры компилируется, естественно, без ошибки, так как компилятор не в состоянии проверить тип объекта Sender. Если Sender является действительно кнопкой, то оператор TEdit(Sender).Text:='Сюрприз!'; не вызывает ошибки на этапе выполнения и изменяет надпись на кнопке, в то время как оператор (Sender as TEdit).Text:=''; вызовет ошибку времени выполнения и, тем самым, предупредит программиста о наличии ошибки в тексте программы. Любопытно отметить, что свойств Text и Modified у класса TButton нет! Объяснить подобный казус я могу только тем, что случайно свойство Text у TEdit находится по тому же адресу (смещению), что и свойство Caption у TButton. Эту гипотезу можно было бы и проверить, сопоставив поля классов TEdit и TButton.

Еще один казус иллюстрирует следующий текст модуля:

unit unitLab4; {Текст модуля иллюстрирует казус, заключающийся

в том, что использованное в программе приведение типа

TMType(MyControl) работать не должно, но работает.

Приведение типа с помощью операции as

(MyControlasTMType)

вызывает, вполне законно, ошибку времени выполнения, так как

MyControl действительно не является потомком TMType}

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

Grids, StdCtrls, ExtCtrls, Menus;

type

TForm1 = class(TForm)

Memo1: TMemo;

Edit1: TEdit;

Button1: TButton;

Label1: TLabel;

Panel1: TPanel;

procedure FormCreate(Sender: TObject);

procedure FormResize(Sender: TObject);

procedure Button1Click(Sender: TObject);

end;

{ Приведенное ниже описание класса TMType необходимо исключительно

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

Кстати, того же эффекта можно было бы добиться и просто так:

TMType=class(TControl)

end;

}

TMType=class(TControl)

public

property Font;

end;

var

Form1: TForm1;

OldWidth,OldHeight:integer;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);

begin

OldWidth:=form1.Width; OldHeight:=form1.Height - 28;

end;

Procedure TForm1.FormResize(Sender: TObject);

var

ScaleX,ScaleY : extended;

MyControl : TControl;

i : integer;

Begin

form1.Caption:=IntToStr(form1.Width) + ' X ' + IntToStr(form1.Height);

ScaleX:= form1.Width / OldWidth;

ScaleY:= (form1.Height - 28)/ OldHeight;

OldWidth:=form1.Width; OldHeight:=form1.Height-28;

for i:=0 to form1.ControlCount-1 do

begin

MyControl:= form1.Controls[i];

// !!!

TMType(MyControl).Font.Size:=round(TMType(MyControl).Font.Size*ScaleY);

// !!!

MyControl.Height := round(MyControl.Height * ScaleY);

MyControl.Width := round(MyControl.Width * ScaleX);

MyControl.Top := round(MyControl.Top * ScaleY);

MyControl.Left := round(MyControl.Left * ScaleX);

end;

End;

END.

Методы класса (Class methods).

Метод класса, в отличие от обычного метода, выполняет операции над классами, а не объектами. Описание метода класса должно начинаться с зарезервированного слова class, например

type

TFigure = class

public

class function Supports(Operation: string): Boolean; virtual;

class procedure GetInfo(var Info: TFigureInfo); virtual;

. . .

end;

Определяющее описание метода класса также должно начинаться с этого слова:

class procedure TFigure.GetInfo(var Info: TFigureInfo);

begin

. . .

end;

В теле метода класса идентификатор Self представляет класс, в котором был вызван метод. Метод класса, определенный в некотором родительском классе, может быть вызван в производных классах. Это значит, что если метод был вызван в классе TC, Self имеет тип TC. С другой стороны, с помощью Self нельзя получить доступ к полям, свойствам и (обычным) методам класса, однако можно вызывать конструктор и другие методы класса.

Метод класса может быть вызван посредством использования ссылки на класс или объект класса. Когда метод класса вызывается с использованием ссылки на объект, Self указывает на класс.

Пример использования метода класса ClassName, который возвращает имя класса:

var s : string;

. . .

s := TSomeClass.ClassName;

if Self.ClassNameIs(s) then …





<< Предыдущая статья
«29. Свойства»
Следующая статья >>
31. Введение в обработку исключительных ситуаций в Delphi