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

«Любой дурак может написать программу, которую поймет компилятор. Хорошие программисты пишут программы, которые смогут понять другие программисты»

Мартин Фаулер

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

29. Свойства

Свойство (property), подобно полю, является атрибутом, некоторой характеристикой объекта. Однако, в отличие от поля, которое просто является областью для хранения некоторого данного, свойство ассоциировано со специфическими действиями по чтению и модификации данных свойства. Значения свойств вообще могут не существовать в памяти, например, они могут быть прочитаны из базы данных, вычислены и т.д.

Свойства обеспечивают контроль над доступом к данным и позволяют достичь двух основных положительных эффектов:

· упрощение доступа к полям данных, которые, в соответствии с парадигмами ООП, должны быть закрытыми (private);

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

Описание свойства определяет его имя, тип и по крайней мере один спецификатор доступа:

property propertyName[indexes]: type index integerConstant specifiers;

Элементы описания свойства имеют такой смысл.

PropertyNameлюбой допустимый идентификатор, являющийся именем свойства.

Элемент [indexes] не является обязательным и используется для описания свойств-массивов.

Элемент описания type должен быть идентификатором типа свойства и не может быть определением типа. Тип свойства может быть одним из следующих:

v любой порядковый, вещественный или строковый;

v множество set;

v класс или интерфейс.

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

Спецификаторы specifiers– это последовательность зарезервированных слов read, write, stored, default (или nodefault) и implements, которая включает и другие, дополнительные идентификаторы.Любое свойство должно иметь по крайней мере один спецификатор read или write. Спецификатор implements относится к интерфейсам и будет рассмотрен позже.

Доступ к свойствам (property access).

Каждое свойство имеет спецификатор read, write или оба. Они называются спецификаторами доступа и имеют такой формат:

read fieldOrMethod

write fieldOrMethod

Здесь fieldOrMethodидентификатор поля или метода, объявленного в том же классе или в родительском классе. Если fieldOrMethodобъявлены в этом же классе, они должны быть описаны выше, т.е. до описания свойства (в классе). Если они объявлены в родительском классе, то должны быть видимы в классе наследнике.

Если fieldOrMethod является полем, то оно должно быть того же типа, что и свойство.

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

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

Таким образом, при наличии такого описания свойства

property Color: TColor read GetColor write SetColor;

метод GetColor должен быть объявлен как

function GetColor: TColor;

и процедура SetColor как

procedure SetColor(Value: TColor);

procedure SetColor(const Value: TColor);

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

Когда свойство встречается в выражении, его значение читается из поля или с помощью функции, определенной спецификатором read. Если свойство появляется в левой части оператора присваивания, его значение записывается в поле или с помощью процедуры, указанных в спецификаторе write.

В приведенном ниже примере объявляется класс TCompass с опубликованным свойством Heading. Значение этого свойства читается из поля FHeading и записывается с помощью процедуры SetHeading.

type

THeading = 0..359;

TCompass = class(TControl)

private

FHeading: THeading;

procedure SetHeading(Value: THeading);

published

property Heading: THeading read FHeading write SetHeading;

. . .

end;

С учетом приведенного описания операторы

if Compass.Heading = 180 then GoingSouth;

Compass.Heading := 135;

соответствуют следующим

if Compass.FHeading = 180 then GoingSouth;

Compass.SetHeading(135);

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

procedure TCompass.SetHeading(Value: THeading);

begin

if FHeading <> Value then

begin

FHeading := Value;

Repaint; // обновить пользовательский интерфейс для

// отображения нового значения

end;

end;

Свойство, чье описание включает только спецификатор read, является свойством только для чтения (read-only property). Если свойство включает только спецификатор write, оно называется свойством только для записи (write-only property). Попытка присвоить значение свойству только для чтения или получить значение свойства только для записи вызывает ошибку.

Свойства-массивы (array properties).

Свойства­-массивы – это индексированные свойства. Они могут представлять такие сущности, как элементы списков, управляющие элементы, принадлежащие некоторому родительскому, пикселы изображения и т.д.

Объявление свойства-массива включает список параметров, который определяет имена и типы индексов, например:

property Objects[Index: Integer]: TObject read GetObject write SetObject;

property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel;

property Values[const Name: string]: string read GetValue write SetValue;

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

TShortSet = set of 0..9;

MyClass = class

private

F1 : integer;

Function PNameRead(i1: string; i2 : single; i3:TObject; i4:ShortSet): string;

public

property PName[ i1 : string;

i2 : single;

i3 : TObject;

i4 : TShortSet]:string read PNameRead;

end;

В частности, свойства-массивы не могут быть опубликованными.

Для свойств-массивов спецификаторы доступа должны быть методами, а не полями. Метод в спецификаторе read должен быть функцией, число, порядок и тип параметров которой должны точно соответствовать списку индексов свойства. Возвращаемое функцией значение должно иметь, разумеется, тип свойства.

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

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

function GetObject(Index: Integer): TObject;

function GetPixel(X, Y: Integer): TColor;

function GetValue(const Name: string): string;

procedure SetObject(Index: Integer; Value: TObject);

procedure SetPixel(X, Y: Integer; Value: TColor);

procedure SetValue(const Name, Value: string);

Доступ к значениям свойств массивов осуществляется путем индексирования имени свойства. Например, операторы

if Collection.Objects[0] = nil then Exit;

Canvas.Pixels[10, 20] := clRed;

Params.Values['PATH'] := 'C:\DELPHI\BIN';

соответствуют

if Collection.GetObject(0) = nil then Exit;

Canvas.SetPixel(10, 20, clRed);

Params.SetValue('PATH', 'C:\DELPHI\BIN');

Определение свойства-массива может сопровождаться директивой default, что делает свойство свойством по умолчанию. Например, если класс описан как

type

TStringArray = class

public

property Strings[Index: Integer]: string ...; default;

end;

доступ к его свойству по умолчанию может быть получен путем простого индексирования имени объекта object[index], что эквивалентно object.property[index]. Для приведенного выше примера выражение StringArray.Strings[7] может быть сокращено до StringArray[7].

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

Спецификатор Index (Index specifiers).

Этот спецификатор предоставляет возможность нескольким свойствам использовать один и тот же метод, возвращающий разные значения (одного типа). Спецификатор Index сопровождается целой константой из диапазона
-2147483647..2147483647. Если свойство имеет спецификатор Index, оно должно иметь методы для доступа к его значениям, а не поля. Например

type

TRectangle = class

private

FCoordinates: array[0..3] of Longint;

function GetCoordinate(Index: Integer): Longint;

procedure SetCoordinate(Index: Integer; Value: Longint);

public

property Left: Longint index 0 read GetCoordinate write SetCoordinate;

property Top: Longint index 1 read GetCoordinate write SetCoordinate;

property Right: Longint index 2 read GetCoordinate write SetCoordinate;

property Bottom: Longint index 3 read GetCoordinate write SetCoordinate;

property Coordinates[Index: Integer]: Longint read GetCoordinate
write SetCoordinate;

. . .

end;

Метод доступа для свойства со спецификатором index должен иметь дополнительный параметр – значение целого типа. Для метода (функции) read это должен быть последний параметр, а для метода write это должен быть предпоследний параметр, т.е. параметр, предшествующий параметру, задающему значение свойства. Когда программа осуществляет доступ к значению свойства, значение (константа) index автоматически передается методу.

С учетом приведенного выше описания код

var

Rectangle : TRectangle;

Rectangle.Right := Rectangle.Left + 100;

соответствует такому вызову метода

Rectangle.SetCoordinate(2, Rectangle.GetCoordinate(0) + 100);

Спецификаторы сохранения (Storage specifiers).

Так называются необязательные директивы stored, default и nodefault. Они не оказывают никакого влияния на работу программы, однако управляют тем, как Delphi поддерживает информацию о типах времени выполнения (RTTI). Конкретно, эти спецификаторы определяют, сохраняет ли Delphi значения опубликованных свойств в файлах форм (.dfm).

Вслед за директивой stored необходимо указать True, False, имя поля булевского типа или имя функции без параметров, которая возвращает значение булевского типа. Например

property Name: TComponentName read FName write SetName stored False;

Если для свойства не указана директива stored, она трактуется как имеющая значение True.

Вслед за директивой default должна следовать константа того же типа, что и тип свойства. Например

property Tag: Longint read FTag write FTag default 0;

Для перекрытия унаследованного значения по умолчанию без задания нового надо указать директиву NoDefault. Директивы default и nodefault поддерживаются для свойств только порядкового типа, а также множественного типа диапазона значений 0..31. Если свойство не имеет директив default или nodefault, оно трактуется как имеющее директиву nodefault.

Когда Delphi сохраняет состояние компонента, она проверяет обсуждаемые спецификаторы для опубликованных свойств. Если текущее значение свойства отличается от его значения по умолчанию (или если значение по умолчанию не задано) и спецификатор stored имеет значение True, то значение свойства сохраняется. В противном случае – нет.

Отметим, что спецификаторы сохранения не поддерживаются для свойств-массивов. Для свойств-массивов директива default трактуется иначе.

Перекрытие и повторное описание свойств (Property overrides and redeclarations).

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

Простейший способ перекрытия состоит в указании только зарезервированного слова property и имени свойства. Этот прием используется для изменения видимости свойства (property’s visibility). Например, если в родительском классе свойство описано как protected, производный класс может переобъявить его в секции public или published.

Повторное описание свойства может включать директивы read, write, stored, default и nodefault, и любая из них перекрывает соответствующую унаследованную. Перекрытие (повторное описание) может изменить унаследованный спецификатор доступа, содержать отсутствующий спецификатор или увеличить видимость свойства, однако оно не может удалить спецификатор доступа или уменьшить видимость свойства. Повторное описание может также включать директиву implements, которая добавляет свойство в список интерфейсов без удаления унаследованных.

Следующие описания иллюстрируют перекрытие описаний свойств.

type

TAncestor = class

protected

property Size: Integer read FSize;

property Text: string read GetText write SetText;

property Color: TColor read FColor write SetColor stored False;

end;

type

TDerived = class(TAncestor)

. . .

protected

property Size write SetSize;

published

property Text;

property Color stored True default clBlue;

. . .

end;

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

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

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

type

TAncestor = class

. . .

property Value: Integer read Method1 write Method2;

end;

TDescendant = class(TAncestor)

. . .

property Value: Integer read Method3 write Method4;

end;

var MyObject: TAncestor;

. . .

MyObject := TDescendant.Create;

Теперь получение или изменение значения свойства MyObject.Value будет выполняться с использованием методов Method1 и Method2, хотя MyObject является экземпляром класса TDescendant. Вместе с тем возможно использование приведения типа MyObject к типу TDescendant для получения доступа к свойствам и соответствующим методам производного класса.

Создание собственного редактора свойства (property editor).

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

Принципиально редактор свойств может иметь один из двух режимов работы (или оба):

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

Разработка редактора свойства выполняется в следующей последовательности:

1 Deriving a property-editor class

2 Editing the property as text

3 Editing the property as a whole

4 Specifying editor attributes

5 Registering the property editor





<< Предыдущая статья
«28. Обработчики сообщений»
Следующая статья >>
30. Ссылки на класс
 
При использовании любых материалов с сайта http://www.introligator.org
обратная ссылка обязательна.
Rambler's Top100 Рейтинг@Mail.ru