Delphi. Урок 23. Свойства

Уроки для начинающих   26 Октябрь 2013  Автор статьи: admin 

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

1
2
3
4
5
6
7
8
9
10
type
TTriangle = class
public
  a, b, c: double;
 
  function Square: double;
  function Perimeter: double;

  constructor Create(NewA, NewB, NewC: double);
end;

Перед вами пример достаточно простого класса «треугольник». Он содержит три поля с соответствующими сторонами и два метода, которые позволяют найти площадь и периметр этого треугольника. В прошлых уроках мы редко рассматривали примеры реализации методов, давайте исправим это недоразумение. Перед вами реализация методов TTriangle.Square и TTriangle.Perimeter, хотя я думаю, что вы и так прекрасно понимаете как можно найти площадь и, тем более, периметр треугольника, зная его стороны.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function TTriangle.Square: double;
var
  p: double; // Полупериметр, пригодится в вычислениях
begin
  // Находим площадь треугольника по формуле Герона
  p := (a + b + c) / 2;
  result := sqrt(p*(p-a)*(p-b)*(p-c));
end;

function TTriangle.Perimeter: double;
begin
  result := a + b + c;
end;

// Конструктор тоже будет выглядеть очень просто:
constructor TTriangle.Create(NewA, NewB, NewC: double);
begin
  a := NewA;
  b := NewB;
  c := NewC;
end;

Все правда совсем просто и не нуждается в дополнительных комментариях.
Но теперь перед нами стоит задача сделать так, чтобы была возможность устанавливать значения сторон треугольника, присваивая ему периметр. Т.к. мы еще не знакомы со свойствами, то реализуем еще один метод, который будет присваивать поля a, b и c в зависимости от переданного ему значения периметра:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type
TTriangle = class
public
  a, b, c: double;
 
  function Square: double;
  function GetPerimeter: double; // Переименуем метод "Perimeter", так будет звучать логичнее
  procedure SetPerimeter(Value: double);

  constructor Create(NewA, NewB, NewC: double);
end;

...

procedure TTriangle.SetPerimeter(Value: double);
begin
  a := Value / 3;
  b := Value / 3;
  c := Value / 3;
end;

Для того, чтобы объединить такие методы GetPerimeter и SetPerimeter в единую структуру, и были придуманы свойства (property). Объявляются они так же как и поля или методы внутри класса, их синтаксис выглядит так:

1
property {Название свойства}: {Тип данных} read {Getter} write {Setter};

С названием свойства и типов данных все понятно — на их месте мы напишем Perimeter и double соответственно. А вот про Getter и Setter нужно поговорить немного подробнее.
[note]Getter — Название метода (обязательно функция) или поля данного класса, которое будет возвращать значение при попытке получить значение от данного property. В случае использования метода — значение будет возвращаться методом-функцией, в случае поля — значение будет доставаться из поля. Используемый метод или поле должны обязательно существовать в классе, иметь описание и реализацию. Тип возвращаемый методом или полем должен обязательно соответствовать типу, указанному в объявлении property в классе.
В случае с реализацией метода в качестве Getter`а появляется возможность каким-то образом модифицировать возвращаемые данные, производить дополнительную обработку или совершить какие-либо изменения в самом экземпляре объекта, т.к. Getter в этом случае — это обыкновенный метод. Главное, чтобы он возвращал значение, как и обычная процедура.[/note]
[note]Setter — Название метода-процедуры данного класса, которая будет устанавливать значение, переданное ей в качестве единственного параметра Value, в какое-либо существующее поле. Само присвоение может быть реализовано любым образом, оно описывается в этом методе по усмотрению программиста. А сам метод должен обязательно содержать единственный параметр с именем Value и с типом, который соответствует типу property. Setter позволяет, например, предварительно преобразовать данные, прежде чем их куда-то присваивать, хотя их даже присваивать куда-то необязательно. Все зависит от цели использования property. Возможно setter просто производит какие-то взаимодействия с полями класса, и эти взаимодействия как-то зависят от переданных в property данных.[/note]
Наверное, вы уже догадались, для чего мы реализовывали методы TTriangle.GetPerimeter и TTriangle.SetPerimeter, потому что они являются самыми классическими примерами Getter`а и Setter`а. Метод GetPerimeter отвечает за возврат значения периметра во внешний код при обращении, а SetPerimeter устанавливает периметр треугольника, имея при этом параметр Value, поделенное на три значение которого затем присваивает сторонам треугольника. Посмотрим, как же будет выглядеть наше свойство (property):

1
2
3
4
5
6
7
8
9
10
11
12
13
TTriangle = class
private
  function GetPerimeter: double;
  procedure SetPerimeter(Value: double);
public
  a, b, c: double;
 
  property Perimeter: double read GetPerimeter write SetPerimeter;

  function Square: double;

  constructor Create(NewA, NewB, NewC: double);
end;

Вот и все, наш property успешно создан!
[important]Прошу обратить внимание на то, что свойства объявляются после объявления полей и перед объявлением методов! И да, почему же наши методы GetPerimeter и SetPerimeter, являющиеся Getter`ом и Setter`ом для свойства Perimeter, мы поместили в область доступа private? А затем, что теперь доступ нам к этим методам из внешнего кода не понадобится, их функциональные возможности теперь можно использовать посредством свойства Perimeter. Их можно и даже нужно скрыть. А нужно их скрыть еще и потому, что при объявлении свойства требуется, чтобы используемые при этом поля и методы были объявлены ранее. А т.к. методы нельзя объявлять перед property (они объявляются после всех property), то удобнее будет поместить группу доступа private выше группы доступа public. Хотя этот побочный эффект можно обойти, но в нашем случае этого делать не нужно.[/important]
Рассмотрим простой пример работы с новым классом и нашим первым свойством:

1
2
3
4
5
6
7
8
9
10
procedure TForm1.FormCreate(Sender: TObject);
var
  t: TTriangle;
begin
  t := TTriangle.Create(10, 11, 12);
  t.Perimeter := 30; // Присваиваем периметр посредством property.
  // Соответственно будет выполнен Setter (SetPerimeter) и присвоено значение сторонам треугольника
  ShowMessage('Стороны треугольника: '+FloatToStr(t.a)+';'+FloatToStr(t.b)+';'+FloatToStr(t.c)+' Периметр: '+FloatToStr(t.Perimeter));
  // Здесь мы опять обращаемся к property Perimeter, это свойство вызывает Getter GetPerimeter, которое и возвращает нам значение
end;

perimeterdemo (1)


Хотелось бы немного заострить внимание на том, что в качестве Getter`а может выступать и поле (которое тоже помещено в private). В этом случае при обращении к значению свойства будет возвращено значение этого поля. При этом Setter может, например устанавливать значение в это скрытое поле, предварительно его обработав, к примеру, если в Setter предусмотрена передача значения в сантиметрах, а поле Getter хранит в себе значение в метрах. Т.е. потребуется сначала провести в Setter`е тривиальное преобразование.
[tip]В описании свойства можно, например, не указывать Getter или Setter. Тогда мы получим в результате свойство, доступное только для записи или чтения соответственно. Выглядеть описания тогда будут так:

1
2
property Perimeter: double read GetPerimeter; // Возможно только чтение из property
property Perimeter: double write SetPerimeter; // Возможно только присвоение в property

Свойства в Delphi имеют еще много различных форм объявлений и работы, но пока многие из них выходят за рамки изученных нами тем, да и к тому же редко используются на практике.
[/tip]
В этом уроке мы рассмотрели свойства (property) в Delphi, которые позволяют реализовать логику обработки данных перед присваиванием значения или перед возвращением значения.

Научиться программировать

  • на Delphi

  • на Java

  • на C++