четверг, 3 мая 2012 г.

Свойства

Перевод из справочной системы Delphi. Оригинал: Properties

О свойствах

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

В объявление свойств включается имя и тип, спецификатор доступа. Синтаксис объявления свойств:

property propertyName[indexes]: type index integerConstant specifiers;
где

propertyName – это любой допустимый идентификатор.

[indexes] – опционален и является последовательностью объявлений параметров, разделяемых точками с запятой (;). Каждое объявление параметра имеет вид: identifier1, ..., identifiern: type.

Тип – должен быть объявленным заранее идентификатором типа. То есть, объявление свойства вида Num: 0..9 ... не является допустимым.

Раздел index integerConstant является опциональным.

specifiers – это последовательность спецификаторов read, write, stored, default (или nodefault), а также спецификаторов реализации. Каждое объявление свойство должно содержать по крайней мере один из спецификаторов read или write.

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


Доступ к свойствам

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

read fieldOrMethod
write fieldOrMethod

где fieldOrMethod - это имя поля или метода, объявленного в этом же классе или в классе-предке.

Если объявление fieldOrMethod присутствует в этом же классе, оно должен предшествовать объявлению свойства. Если оно присутствует в классе-предке, оно должно быть видно из класса-наследника. То есть это не может быть объявлением поля или метода с видимостью private в классе-предке, объявленном в другом модуле.

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

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

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

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

Например, при объявлении:

 property Color: TColor read GetColor write SetColor;

Объявление метода GetColor должно иметь вид:

 function GetColor: TColor;

а метод SetColor может быть объявлен одним из нижеприведенных способов:

 procedure SetColor(Value: TColor);
 procedure SetColor(const Value: TColor);
 //Название параметра, передаваемого SetColor', естественно необязательно должно быть именно таким

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

В следующем примере объявляется класс с именем TCompass, который имеет свойство Heading с видимостью published. Значение свойства 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);

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

 procedure TCompass.SetHeading(Value: THeading);
 begin
   if FHeading <> Value then
     begin
       FHeading := Value;
       Repaint;    // перерисовать интерфейс для отображения нового значения
     end;
 end;

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


Свойства-массивы

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

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

 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;

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

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

Методы доступа для свойств-массивов из предыдущего примера могут быть объявлены следующим образом:

 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:\BIN';

Что соответствует:

 if Collection.GetObject(0) = nil then Exit;
 Canvas.SetPixel(10, 20, clRed);
 Params.SetValue('PATH', 'C:\BIN');

За объявлением свойства-массива может следовать директива default, которая обозначает, что это свойство становится свойством класса по умолчанию. То есть:

 type
    TStringArray = class
     public
        property Strings[Index: Integer]: string ...; default;
           ...
     end;

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


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

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

 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;

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

Для приведенного выше объявления, если Rectangle относится к типу TRectangle, то инструкция:

 Rectangle.Right := Rectangle.Left + 100;

соответствует:

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

Спецификаторы хранения

Опциональные директивы stored, default и nodefault называются спецификаторами хранения. Они не влияют на поведение программы, а отвечают за сохранение значений свойств с видимостью published в файлах хранения описания форм.

За директивой stored должно следовать True, False, имя поля типа Boolean, или имя метода без параметров, возвращающего значение типа Boolean. Например:

 property Name: TComponentName read FName write SetName stored False;

Если у свойства нет директивы stored, оно обрабатывается так же как свойство с директивой stored True. За директивой default должна следовать константа, совпадающая по типу со свойством. Например:

 property Tag: Longint read FTag write FTag default 0;

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

Замечание. Запрещено использовать порядковое значение 2147483648 как значение default. Это значение зарезервировано для представления директивы nodefault.

При сохранении состояния компонента, проверяются спецификаторы хранения свойств, имеющие видимость published. Если текущее значение свойства отличается от значения в директиве default (или указана директива nodefault), а для спецификатора stored установлено значение true, значение свойства сохраняется. В противном случае – нет.

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

Перекрытие и повторное объявление свойств

Объявление свойства, в котором не указывается тип, называется перекрытием свойства. Перекрытие свойства позволяет вам изменять наследуемую область видимости свойства или его спецификаторы. Простейшее перекрытие свойства состоит только из ключевого слова property, за которым следует идентификатор свойства. Такая форма объявления применяется для изменения области видимости свойства. Например, если класс-предок объявляет свойство с областью видимости 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 добавляет спецификатор записи для разрешения изменения значения свойства. Перекрытие свойств Text и Color изменяет область видимости с protected на published. Перекрытие свойства Color определяет, что значение свойства должно сохраняться в том случае, если оно не равно clBlue.

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

Когда свойство скрыто или перекрыто в классе-предке, обращение к нему всегда является статическим, то есть объявленный в момент компиляции тип переменной, используемый для идентификации объекта, определяет интерпретацию идентификаторов его свойств. После выполнения следующего кода, несмотря на то, что MyObject содержит значение типа TDescendant, чтение или присваивание значения MyObject.Value запускает Method1 или Method2. Тем не менее, вы можете преобразовать тип MyObject в TDescendant для обращения к свойствам класса-потомка и их спецификаторам доступа:

 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;

Свойства класса

К свойствам класса можно обращаться без ссылки на объект. Обращение к свойствам класса может производиться только через методы класса, объявленные как class static или через поля, объявленные как class fields. Свойство класса объявляется при помощи ключевых слов class property. Свойства класса не могут иметь область видимости published, не могут иметь инструкций stored или объявления значений default.

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

  • Еще одним блоком объявлений class var;
  • Объявлением процедуры или функции (то есть метода) (включая процедуры класса и функции класса);
  • Объявлением свойства (включая свойства класса);
  • Объявлением конструктора или деструктора;
  • Спецификатором видимости (public, private, protected, published, strict private, and strict protected).

Например:

type
    TMyClass = class
      strict private
        class var         // Note fields must be declared as class fields
           FRed: Integer;
           FGreen: Integer;
           FBlue: Integer;
        public             // ends the class var block
           class property Red: Integer read FRed write FRed;
           class property Green: Integer read FGreen write FGreen;
           class property Blue: Integer read FBlue write FBlue;
    end;

Обращаться к этим свойствам класса можно следующим образом:

 TMyClass.Red := 0;
 TMyClass.Blue := 0;
 TMyClass.Green := 0;

1 комментарий:

  1. Спасибо за материал! Есть с чего подступиться к применению свойств на практике

    ОтветитьУдалить