Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ООП.doc
Скачиваний:
23
Добавлен:
08.11.2018
Размер:
1.4 Mб
Скачать
  1. Свойства

Как уже было сказано, правило инкапсуляции утверждает, что для обеспечения надежности нежелателен прямой доступ к данным: чтение и обновление их содержимого должно производиться вызовом соответствующих методов. Эти методы образуют интерфейс объекта. Но, как правило, бывает необходимо не только вызывать методы объекта, но и считывать или устанавливать какие-либо его данные, параметры или свойства. Например, цвет графического элемента, место работы человека и т.п. Предоставить для этих целей поля с областью видимости public не соответствует идее инкапсуляции – данные должны изменяться путем вызова методов. Кроме того, возможно, необходимы некоторые дополнительные действия – например при изменении цвета графического элемента следует не только записать значение поля, но и перерисовать этот объект на экране. Следовательно, нужна пара методов – метод чтения и метод записи.

В языке Object Pascal имеется соответствующая конструкция, которая объединяет методы чтения и записи под одним именем. Такая конструкция называется свойством. Обычно свойство определяется тремя элементами: объявлением собственно свойства и двумя методами, которые осуществляют его чтение и запись. Объявляется свойство с использованием директивы property следующим образом:

property Имя_свойства: Тип_свойства read Метод_чтения write Метод_записи спецификаторы;

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

Как правило, само свойство объявляется в области видимости public, или более высокой. Методы доступа к нему объявляют с более низкой видимостью, например private.

Рассмотрим пример объявления свойства:

Type TSomeObject = class private ... function GetAProperty: TSomeType; //метод чтения procedure SetAProperty(Value: TSomeType); //метод записи public // Объявление собственно свойства property AProperty: TSomeType read GetAProperty write SetAProperty; end;

В приведенном примере доступ к значению свойства AProperty осуществляется через вызов методов GetAProperty и SetAProperty.

Способ образования имен методов чтения и записи свойств путем добавления Get и Set к имени свойства не обязателен. Он используется для повышения читаемости программы.

При необходимости прочитать или установить свойство в обращении к этим методам в явном виде нет необходимости, достаточно написать:

SomeObject.AProperty := AValue; AVariable := SomeObject.AProperty;

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

SomeObject.SetAProperty (AValue); AVariable := SomeObject.GetAProperty;

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

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

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

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

Type TSomeObject = class private FProperty: TSomeType; //для хранение значения свойства //используется поле procedure SetAProperty(Value: TSomeType); //метод записи public // Объявление собственно свойства property AProperty: TSomeType read FProperty write SetAProperty; // При записи значения свойства здесь выполняются // дополнительные действия, поэтому используется метод // записи. При чтениии дополнительных действий нет – // поэтому указывается имя поля end;

procedure TSomeObject.SetAProperty(Value: TSomeType); //метод записи свойства begin //реализуется проверка допустимости значений if (Value>0) and (Value<100) then FProperty := Value; end;

Рассмотрим далее в качестве примера класс оконного элемента, обладающего таким свойством, как цвет:

TScreenElem = class private //поле для хранения значения свойства FColor: TColor; //служебный метод проверки допустимости значения свойства function Correct(C: TColor): Boolean; //служебный метод перерисовки экранного элемента procedure Paint; //метод записи свойства procedure SetColor (NewColor: TColor); public //свойство цвет экранного элемента property Color: TColor read FColor write SetColor; end;

...

//метод записи свойства procedure TScreenElem.SetColor(NewColor: TColor); begin //выполняется проверка допустимости значения цвета if(NewColor<>FColor) and Correct(NewColor) then begin FColor := NewColor; //выполняется перерисовка экранного элемента новым цветом Paint; end; End;

В этом примере чтение значения свойства цвета экранного элемента Color означает просто чтение поля FColor, поскольку нет необходимости ни в каких дополнительных действиях. Зато при присвоении значения внутри SetColor вызывается сразу два метода: проверки корректности заданного значения цвета Correct и перерисовки экранного элемента Paint.

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

Type TSomeObject = class ... property AProperty: TSomeType read GetValue; end;

В этом примере вне объекта значение свойства можно лишь прочитать; попытка присвоить свойству AProperty значение вызовет ошибку компиляции.

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

TSObStack = class private //массив элементов типа TElem Stack: array[1..100] of TElem; //индекс вершины стека Top: integer; public procedure InStack(Elem: TElem); function OutStack(var Elem: TElem): boolean;

property Num: integer read Top; // Свойство - количество //элементов стека. Можно только читать – возможность записи //отсутствует. При чтении просто возвращается значение поля Top ... end;

Обобщим преимущества от использования механизма свойств:

  1. Реализация инкапсуляции данных. Методы чтения и записи свойства отделяют код приложения, использующего объекты данного класса, от конкретного способа хранения данных. И при изменении способа хранения данных, например поле/динамика/файл/база данных, нет необходимости изменять клиентский код.

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

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

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

Рассмотрим далее спецификаторы, используемые при объявлении свойств. Наиболее часто используемым спецификатором свойства является значение по умолчанию. Для объявления значения по умолчанию используется ключевое слово default:

Property Visible: boolean read FVisible write SetVisible default True;

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

Для обобщения всего вышесказанного, рассмотрим пример класса с использованием свойств. Этот класс будет инкапсулировать данные о человеке: фамилию и дату рождения. В классе будут определены три свойства: имя, дата рождения и возраст. При установке значения свойства «Имя» все символы имени преобразуются в верхний регистр – для упрощения дальнейшего использования, например, сравнения. При его чтении никаких дополнительных действий не выполняется. При установке значения свойства «Дата рождения» проверяется, не превосходит ли дата текущую. В этом случае предполагается, что дата введена ошибочно, и она заменяется на текущую. При чтении дополнительных действий не выполняется. Свойство «Возраст» рассчитывается по значению даты рождения.

interface

type TPersonData = class private FName: string; //поле имени FDateOfBirth: TDate2; //поле даты рождения //метод записи имени procedure SetName(Value: string); //метод записи даты рождения procedure SetDateOfBirth (Value: TDate); //метод чтения возраста function GetAge: integer; public //свойство имя property Name: string read FName write SetName; //свойство дата рождения property DateOfBirth: TDate read FDateOfBirth write SetDateOfBirth; //свойство возраст property Age: integer read GetAge; end;

implementation //реализация метода записи имени procedure TPersonData.SetName(Value: string); begin //преобразуем полученное значение к верхнему регистру //и записываем в поле FName := AnsiUpperCase(Value); end;

//реализация метода записи даты рождения procedure TPersonData.SetDateOfBirth (Value: TDate); var CurrentDate: TDate; begin //считываем системную дату CurrentDate := Date; //сравниваем полученное значение с системной датой if (Value>CurrentDate) then //записываем в поле системную дату FDateOfBirth := CurrentDate else //записываем в поле полученное значение FDateOfBirth := Value end;

//реализация метода чтения возраста function TPersonData.GetAge: integer; begin //возвращаем возраст, как разность между текущей датой //и датой рождения //специаоьного поля для хранения возраста нет result := round(Date - DateOfBirth); end;

Рассмотрим, каким образом организованы свойства в этом примере. Свойства «Имя» и «Дата рождения» требуют дополнительных действий при их записи, поэтому организованы соответствующие методы. Значения свойств хранятся в соответствующих полях. При чтении свойств дополнительные действия не нужны, поэтому вместо методов чтения после директивы read указывается непосредственно имя поля. Значение свойства «Возраст» вычисляется на основе значения даты рождения, а не устанавливается извне. Поэтому для него нет возможности записи, и директива write в его объявлении отсутствует. Поля для хранения возраста нет. Метод чтения рассчитывает значение возраста, используя поле даты рождения. В качестве альтернативного варианта можно было организовать поле возраста и устанавливать его в методе записи даты рождения.

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