Шаг 134.
Простые свойства

    На этом шаге мы рассмотрим правила записи и использования простых свойств.

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

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TVerySimpleProperty = class
       FField : Integer;
       property VerySimpleProperty : Integer read FField write FField;
  end;


var
  Form1: TForm1;
  MyVar : TVerySimpleProperty;
  St : String;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyVar := TVerySimpleProperty.Create;
  MyVar.VerySimpleProperty := 10;
  Str (MyVar.VerySimpleProperty, St);
  Form1.Memo1.Lines.Add(St);
  MyVar.Free;
end;

end.
Текст этого примера можно взять здесь.

    Результат:


Рис.1. Результат работы приложения

    В этом примере выполняются только элементарные действия:

    Как отмечалось выше, свойство служит для решения двух задач: обеспечения дополнительного уровня защищенности полей объектов и обеспечения доступности свойств на этапе проектирования через Object Inspector. Простейшие свойства, в таком виде, как они описаны в примере, ни первую, ни вторую задачу фактически не решают, поскольку получается только подмена одного идентификатора (FField) другим (VerySimpleProperty).

    Поэтому логично будет те поля классов, обращение к которым сделано с помощью свойств, включать в приватную (private) или защищенную (protected) часть класса, а само свойство - в часть общедоступных (public) или опубликованных (published) описаний.

    Причем свойство описывается общедоступным, если преследуется цель решения первой задачи, а опубликованным, - если обеих. Именно так делается во всех стандартных классах Object Pascal. Но, само собой разумеется, что "это - не догма, а руководство к действию". Таким образом, стандартный вид описания простых свойств будет следующим:

type
  TVerySimpleProperties  =  class 
    private
      FField1   :   Integer;    { Поле  первого   свойства   }
      FField2   :   Integer;    { Поле  второго  свойства    } 
    protected
      FField3   :   Integer;    { Поле   третьего  свойства  }
    public
      {   Первое   свойство   } 
      property VerySimpleProperty1 : Integer
          read FField1 write FField1; 
    published
      {   Второе   свойство   } 
      property VerySimpleProperty2 : Integer 
           read FField2 write FField2; 
      {   Третье   свойство   } 
      property VerySimpleProperty3 : Integer 
           read FField3 write  FField3; 
  end;

    Поскольку при этих описаниях получается, что для обеспечения инкапсуляции свойство используется вместо методов. Однако в таком виде свойство поддерживает "неинтеллектуальную" работу с полем - только прямое чтение и прямую запись. Для обеспечения гибкого доступа к полю по чтению и записи в синтаксисе свойства предусмотрена возможность указания имен методов после директив read и write, которые в рамках свойств называются спецификаторами доступа (access specifiers).

    В результате, соединение возможностей таких языковых конструкций, как свойства, директивы области видимости (private, protected, public и published), и методы в качестве спецификаторов read и write, позволяет достичь гибкого и надежного механизма скрытия информации в рамках класса.

    Таким образом, структура хорошо реализованного свойства приобретает вид, показанный в следующем примере:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TSimpleProperty2 = class
    private
      FField : Integer;  { Поле свойства }
    protected
      { Заголовок метода для записи значения свойства }
      procedure SetSimpleProperty2 (Value : Integer);
      { Заголовок метода для чтения значения свойства }
      function GetSimpleProperty2 : Integer;
    public
      { Описание свойства }
      property SimpleProperty2 : Integer 
           read GetSimpleProperty2 { Указание метода для чтения }
           write SetSimpleProperty2; { Указание метода для записи }
   end;



var
  Form1: TForm1;
  MyVar : TSimpleProperty2;
  St : String;

implementation

{$R *.dfm}

procedure TSimpleProperty2.SetSimpleProperty2;
begin
  FField := Value
end;

function TSimpleProperty2.GetSimpleProperty2;
begin
  Result := FField
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyVar := TSimpleProperty2.Create;
  MyVar.SimpleProperty2 := 10;
  {!!! Обращение к свойству остается таким же, как и в }
  { случае простейшего свойства !!!}
  Str (MyVar.SimpleProperty2, St);
  Form1.Memo1.Lines.Add(St);
  MyVar.Free;
end;

end.
Текст этого примера можно взять здесь.

    Рассматривая этот пример, сделаем несколько замечаний.

    Хотя способ доступа к полю свойства сделан уже не прямо по идентификатору, а с помощью методов, работа со свойством остается такой же, как и раньше! Здесь проявляется одна важная особенность работы компилятора.

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

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

    MyVar.SetSimpleProperty2 (10);

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

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

TSimpleProperty2  = class
  private
    FField : Integer;    { Поле свойства }
  protected
    { Заголовок метода для записи значения свойства } 
    procedure SetSimpleProperty2 (Value : Integer);
  public
    {   Описание   свойства   } 
    property SimpleProperty2   :   Integer 
      read FField   {Прямое  обращение   к  полю для  чтения} 
      write SetSimpleProperty2;   {Указание  метода  для  записи} 
  end;

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

    Еще одно замечание касается случая, если описание одного из спецификаторов доступа read или write опущено.

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

    Теперь остановимся на одной особенности свойств, которая зачастую вводит в заблуждение начинающих изучать Object Pascal. Эта особенность состоит в "обманчивости" стандартной директивы default.

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

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

    При описании свойств-массивов слово default является директивой умолчания, отделяется от предыдущих описаний точкой с запятой, после него значение никогда не указывается, а означает оно, что идентификатор данного свойства-массива при обращениях к свойству можно не записывать - он будет взят по умолчанию. Более подробно свойства-массивы рассматриваются в шаге 136.

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

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

unit Unit1;
{ Тест, демонстрирующий отсутствие влияния спецификатора  }
{ default на значение простого свойства, устанавливаемое }
{ по умолчанию при инициализации. }
interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

  TDefaultIntProperty = class
    private
      FInt : Integer;
      procedure SetDefaultIntProperty (Value : Integer);
    public
      property DefaultIntProperty : Integer 
         read FInt write SetDefaultIntProperty default 10; 
                      { default определяет значение 10,       }
                      { но поле Fint будет иметь значение 0 ! }
  end;

  TDefaultBoolProperty = class
    private
      FBool : Boolean;
      procedure SetDefaultBoolProperty (Value : Boolean);
    public
      property DefaultBoolProperty : Boolean 
            read FBool
            write SetDefaultBoolProperty default True; 
                   { default определяет значение True,          }
                   { но поле FBool будет иметь значение False ! }
   end;

var
  Form1: TForm1;
  MyIntVar : TDefaultIntProperty;
  MyBoolVar : TDefaultBoolProperty;
  St : String;

implementation

{$R *.dfm}

procedure TDefaultIntProperty.SetDefaultIntProperty;
begin
  FInt := Value
end;

procedure TDefaultBoolProperty.SetDefaultBoolProperty;
begin
  FBool := Value
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  MyIntVar := TDefaultIntProperty.Create;
  Str (MyIntVar.DefaultIntProperty, St);
  Form1.Memo1.Lines.Add(St);
  MyIntVar.Free;

  MyBoolVar := TDefaultBoolProperty.Create;
  If MyBoolVar.DefaultBoolProperty then
        Form1.Memo1.Lines.Add( 'True' )
  else Form1.Memo1.Lines.Add( 'False' );
  MyBoolVar.Free;
end;

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


Рис.2. Результат работы приложения

    Как видно из данного примера, несмотря на то, что спецификатор default устанавливает для свойства типа Integer значение 10, а для свойства типа Boolean - значение True, при выполнении проекта на экран выводятся 0 и False, то есть значения, полученные свойствами при создании и инициализации объектов MyIntVar и MyBoolVar. Дело в том, что default в рамках описания простого свойства является одним из, так называемых, спецификаторов сохранения (storage specifiers), к которым принадлежат также stored и nodefault. Эти спецификаторы могут быть описаны только для свойств, не являющихся составными структурами данных, и используются библиотекой VCL для управления автоматическим сохранением значений опубликованных свойств в файле формы.

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

    Рассмотрим смысл спецификаторов сохранения. Когда происходит сохранение в файле формы состояния компонентов, то просматриваются все опубликованные свойства. Если такое свойство не имеет спецификатора stored или имеет его, а значение булевской величины, стоящей после него, равно False, то свойство не сохраняется. Если же результат равен True, то текущее значение свойства сравнивается со значением, установленным по умолчанию спецификатором default. Если эти значения равны, то свойство не сохраняется, а если не равны или default отсутствует, то сохраняется. Спецификатор nodefault имеет чисто декоративный смысл и обозначает отсутствие спецификатора умолчания default.

    Пример корректного описания спецификаторов сохранения:

TDefaultIntProperty =  class 
  private
    FInt   :   Integer;
    procedure  SetDefaultlntProperty   (Value   :   Integer); 
    function PropertyStored   :   Boolean; 
  published
    property DefaultIntProperty   :   Integer 
        read FInt write  SetDefaultIntProperty stored PropertyStored default  10; 
  end;
implementation

procedure TDefaultIntProperty.SetDefaultIntProperty; 
begin
  FInt   := Value 
end;

function TDefaultIntProperty.PropertyStored; 
begin
  { В общем случае здесь может располагаться любой код }
  Result := True 
end;

    На следующем шаге мы рассмотрим переопределение свойств.




Предыдущий шаг Содержание Следующий шаг