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

Векторные свойства

Рассмотренные выше свойства являются скалярными. Кроме них в языке Object Pascal существуют векторные свойства. Объявляется векторное свойство следующим образом:

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

Для векторного свойства необходимо описать не только тип элемента, но также имя и тип индекса или индексов. Тип индекса может быть любым: перечислимым (integer, char), неперечислимым (real), составным (string или даже класс), в отличие от массивов, которые могут быть проиндексированы только перечислимыми типами. Например:

property Data[Index: Real]: integer read GetData write SetData;

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

property PersonData[const Name: string]: TPersonData read GetPersonDta write SetPersonData;

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

function GetData(Index: Real): integer;

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

function GetPersonData(const Name: string): TPersonData;

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

procedure SetData(Index: Real; Value: integer);

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

procedure SetPersonData(const Name: string; Value: TPersonData);

Доступ к векторным свойствам осуществляется с помощью индексов. Например:

if SomeDataObject.Data[-1.0] = -1 then Exit;

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

PersonList.PersonData['Иванов'] := PersonData;

Эти обращения будут оттранслированы компилятором в вызов методов:

if SomeDataObject.GetData(-1.0) = -1 then Exit;

Image.SetPixel(10, 20, clRed);

PersonList.SetPersonData('Иванов', PersonData);

Можно вызывать методы непосредственно, но это нерационально.

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

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

interface Type

TList = class private //количество записей n: byte; //массив фамилий FName: array[1..100] of string; //массив оценок FMark: array[1..100] of byte; //метод чтения function GetMark(name: string): byte; //метод записи procedure SetMark(name: string; value: byte); public //свойство - оценки property Mark[name: string]: byte read GetMark write SetMark; //свойство – количество оценок. Только для чтения! property Number: byte read n; ... end;

Var List: TList

implementation

//реализация метода чтения function TList.GetMark(name: string): byte; var i: byte; begin result := 0; //ищем в массиве фамилий равную заданной и возвращаем //соответствующую оценку for i := 1 to n do if name = FName[i] then result := FMark[i]; end;

procedure TList.SetMark(name: string; value: byte); var i: byte; NewFam: Boolean; begin //Признаку того, что человек новый, присваиваем истину NewFam := true; //Ищем совпадающую фамилию for i := 1 to n do if name = FName[i] then begin //Нашли – редактируем оценку NewFam := false; FMark[i] := value; end; //Не нашли – добавляем новую if (NewFam) then begin //Увеличиваем на единицу количество записей //При создании объекта оно устанавливается в 0 //Проверка выхода за границу массива не производится n:=n+1; //Записываем данные в массивы FMark[n] := mark; FName[n] := name; end; end;

procedure Test; begin ... List.Mark[‘Иванов’] := 5; ... if List.Mark[‘Петров’] < 4 then ... end;

Из этого примера видно, что внутреннее представление данных может существенно отличаться от вида самого векторного свойства. Информация о фамилиях и оценках хранится в двух массивах, причем индексы соответствующих фамилии и оценки одинаковы. Метод чтения свойства ищет в массиве фамилий ту, которая совпадает с заданным индексом векторного свойства, и возвращает оценку из массива оценок с соответствующим индексом. Метод записи свойства также ищет в массиве фамилий фамилию, совпадающую с заданным индексом векторного свойства. Если она найдена – редактирует оценку, иначе добавляет присваиваемое свойству значение в массив оценок, а значение индекса в массив индексов. В качестве альтернативного варианта можно было бы, например, организовать хранение данных в динамической цепочке записей. Также у класса организовано свойство Number – количество оценок. Его предназначение – позволить внешнему миру узнать, сколько записей имеется в ведомости. Оно открыто только для чтения, поскольку изменяется только в результате выполнения метода – при добавлении человека в ведомость. Оно возвращает значение поля n. Предоставить само поле n для чтения внешним миром, сделав его public, было бы нерационально. Поскольку, в этом случае внешний мир мог бы его изменить, и его значение перестало бы соответствовать истинному количеству записей.

У векторных свойств есть еще одна важная особенность. Если векторное свойство описано с ключевым словом default, то предполагается, что этот класс «построен» вокруг основного векторного свойства. Основная цель такого класса – обеспечение реализации этого векторного свойства, все остальное является вспомогательным. Это свойство будет свойством данного класса «по умолчанию». Понятно, что объявить свойством по умолчанию можно только одно свойство. Если у объекта есть такое свойство, то при обращении можно его не упоминать, а ставить индекс в квадратных скобках сразу после имени объекта:

interface Type

TMyObject = class; ... property Data[Index: integer]: string read GetData write SetData; default; end;

var AMyObject: TMyObject;

implementation

... Procedure Test; begin ... //Обращаемся к векторному свойству указывая его имя AMyObject.Data[1] := ‘Data 1’; //Но можем имя свойства и опустить AMyObject[2] := ‘Data2’; ... end;

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

Рассмотренный выше пример с экзаменационной ведомостью прекрасный кандидат на то, чтобы его основное свойство – отметка – было объявлено как свойство по умолчанию:

property Mark[name: string]: integer read GetMark write SetMark; default;

Тогда работа с объектом такого класса будет выглядеть следующим образом:

List[‘Иванов’] := 5; ... if List[‘Петров’] < 4 then ...

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

Следующий пример демонстрирует работу с векторным свойством, объявленным с ключевым словом default.

interface Type

numbers = 1..100;

TMyObject = class privare a:array[numbers,numbers] of real; function GetA(num1,num2: numbers): real; procedure SetA(num1,num2: numbers; val: real); public property AProp[number1, number2: numbers]: real read GetA write SetA; default; ... end;

var My: TMyObject; A: real;

implementation

... Procedure Test; Begin ... //Допустимая запись, но default не используется My.AProp[1,1] := 10;

//Допустимая запись, использующая default My[2,2] := 20;

//Запись, синтаксически правильная в пределах модуля, //но крайне не рекомендуемая My.a[3,3] := 30;

//Допустимая в пределах модуля запись, не использующая //механизм свойств My.SetA(4,4,40); ... //Допустимая запись, но default не используется x := My.AProp[1,1];

//Допустимая запись, использующая default x := My[2,2];

//Запись, синтаксически правильная в пределах модуля, //но крайне не рекомендуемая x := My.a[3,3];

//Допустимая в пределах модуля запись, не использующая //механизм свойств x := My.GetA(4,4); ... End;

Итак, можно обращаться непосредственно к полям, где хранятся данные. Но, во-первых, это противоречит принципу инкапсуляции – сокрытию особенностей реализации, а во-вторых, за пределами модуля, где объявлен класс это невозможно, поскольку область видимости у полей в данном примере private. Можно явно вызывать методы чтения и записи свойств. Требованиям инкапсуляции это не противоречит, но механизм свойств не используется. Кроме того, в данном примере методы также имеют область видимости private. Рекомендуется работать непосредственно с самим векторным свойством, либо указывая имя свойства, либо используя это свойство как свойство класса по умолчанию.

О роли свойств красноречиво говорит следующий факт: в Borland Delphi у всех имеющихся в распоряжении программиста стандартных классов все поля недоступны. Для тех характеристик состояния объекта, которые должны быть доступны из внешнего мира, организованы свойства. Некоторые из них позволяют как считывать значения, так и записывать, некоторые открыты только для чтения. Рекомендуется при разработке собственных классов придерживаться этого же правила.

    1. Доступ к данным в C++

Стандарт языка C++ не предусматривает механизма свойств. В некоторых компиляторах, например Borland C++ Builder, реализовано расширение стандарта языка, предоставляющие возможности, аналогичные свойствам в Object Pascal. Но это расширение не является стандартным, зависит от компилятора и здесь не рассматривается.

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

Например:

class CDate { private: int month, day, year; public: void set(int m, int d, int y); //метод записи void get(int& m, int& d, int& y); //метод чтения void print(); };

void CDate::set(int m, int d, int y); //метод записи { month = m; //проверка допустимости значений if (month<1) month = 1; if (month>12) month = 12;

day = d; //проверка допустимости значений //количество дней в месяце не учитывается if (day<1) day = 1; if (day>31) day = 31;

year = y; //проверка допустимости значений if (year<0) year = 0; if (year>3000) yaer = 3000; }

void CDate::get(int& m, int& d, int& y); //метод чтения { m = month; d = day; y = year; }

void test() { CDate date; //статический объект int m,d,y;

//Допустимый способ установить значения данных date.set(10, 15, 2004);

//Недопустимый способ установить значения данных //Поля month, day, year имеют область видимости private //и недоступны ниоткуда кроме методов класса: // date.month = 11;

...

//Допустимый способ прочитать значения данных date.get(m, d, y);

//Недопустимый способ прочитать значения данных // m = date.month; }

Также можно организовать работу с векторными данными.