Перфолента.NET. ООП. Конструируем класс. Свойства

Материал из ТХАБ.РФ
Перейти к: навигация, поиск

Конструируем класс. Свойства.

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

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

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

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

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

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

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

Свойства

Свойство можно представить, как своеобразную «обёртку» над Полем. Свойство имеет оба или только один из методов Получить / Установить, а также может иметь одно или несколько Полей для хранения связанных со Свойством данных. Свойство может не иметь «своих» Полей, а оказывать влияние на Поля других Свойств или на Поля, логически не связанные со свойствами, но такое поведение встречается довольно редко.

В языке Перфолента для создания Свойства существуют 2 разных синтаксиса – полный и краткий (авто).

  • Полный позволяет контролировать все аспекты работы Свойства, но содержит много кода по сравнению с Полем.
  • Краткий способ (автоматический) позволяет компилятору автоматически добавить скрытое Поле для хранения значения и дописать код методов Получить/Установить, делая синтаксис определения свойства почти не отличимым от определения поля.

Что бы понять конструкцию Свойства сначала рассмотрим полный синтаксис:

Класс Собака
       &ВидноВсем
       Свойство Полное Имя тип Строка
               //скрытое поле для хранения значения свойства
               Поле _Имя тип Строка = "Без имени"
               //метод для получения значения свойства
               Получить
                           Возврат _Имя
               КонецПолучить
               //метод для установки значения свойства
               Установить(НовоеЗначение тип Строка)
                           _Имя=НовоеЗначение
               КонецУстановить
       КонецСвойства
КонецКласса
Важно: ключевое слово Полное - является обязательным.

Как видите, по сравнению с Полем кода стало значительно больше. Однако, у нас появилось 2 метода Получить и Установить, в которых мы можем делать любые проверки, например, корректно ли новое значение, или можем выполнить какие-то действия необходимые при изменении значения свойства.

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

Поле, принадлежащее Свойству, является скрытым от внешнего наблюдателя, но ни что не мешает сделать его видимым, например, в пределах сборки (что за сборка? - Это модуль ??) или для наследников Класса. В некоторых случаях это позволяет сэкономить на вызове метода, когда значение Свойства изменяется разработчиком Класса, который хорошо знает, что делает, особенно при массовом изменении значений свойств объекта.

Теперь посмотрим на краткий (автоматический) способ определения Свойства:

Класс Собака 
       &ВидноВсем // Свойство Вес - можно будет читать и изменять при использовании Объекта
       Свойство Авто Вес тип Целое = 0 
// При создании Объекта Класса Собака полю Вес сразу будет присвоено значение 0, которое можно будет изменять и читать 
КонецКласса

Почти не отличается от определения поля, правда? Особенно, если учесть, что ключевое слово Авто можно пропустить. Инициализацию свойства тоже можно пропустить. А вот ключевое слово Полное в предыдущем примере пропускать нельзя!

Так же, как и для полей, краткий синтаксис определения свойства позволяет определять несколько свойств одновременно:

&ВидноВсем Свойство Фамилия, Имя, Отчество тип Строка = "<>" 

Умнику на заметку: не забывайте, что инициализирующее выражение вычисляется столько раз, сколько свойств инициализируется.

Глядя на это определение, у многих возникает вопрос: «А зачем вместо поля определять свойство, если ни каких дополнительных проверок при этом не выполняется, а данные всё равно хранятся в скрытом поле? Излишество!». И да и нет. Поле было бы экономичнее, т.к. отсутствует излишний вызов методов при получении и установке значения, однако, если в последствии вам понадобится проверка значения и вы решите изменить поле на свойство, вы сломаете работу ранее откомпилированных программ, ожидающих поле, а не свойство. Как вы понимаете, особенно это актуально для библиотек. Вам придется перекомпилировать абсолютно все программы, использующие библиотеку, если вы измените поле на свойство!

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

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

Методы Получить и Установить

Свойство обязательно имеет один или оба метода Получить/Установить.

В полном синтаксисе определения свойства Вы обязаны вручную создать хотя бы один из этих методов, а в кратком (авто) синтаксисе компилятор автоматически сделает это за вас.

При обращении к свойству из кода программы, компилятор неявно вызывает методы Получить/Установить в тот момент, когда Вы хотите получить или установить значение свойства. Явно эти методы вызывать нельзя, т.к. их как бы не существует. С точки зрения вызывающего программиста свойства выглядят как поля.

Умнику на заметку: на самом деле, «под капотом Net» эти методы существуют, но имеют специальные имена get_ИмяСвойства и set_ИмяСвойства. При желании их можно вызвать по этим именам.

Собака2.set_Имя("Лайка")
ВыводСтроки Собака2.get_Имя

Рассмотрим пример установки значения свойства.

МояСобака.Имя = "Жучка"    //тут будет вызван метод Установить(НовоеЗначение), которому в качестве нового значения будет передано значение «Жучка»

А теперь пример получения значения свойства.

ВыводСтроки МояСобака.Имя    //тут будет вызван метод Получить(), который вернет значение «Жучка»

При кратком синтаксисе определения свойства компилятор сам создаёт необходимые методы Получить/Установить, но вызываться при получении или установке значения они будут точно так же, как и при полном определении. Для того, кто использует свойство, не важно, как оно было создано, кратким или полным синтаксисом.

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

       &ВидноВсем
       Свойство Полное РазмерФайла тип Целое
               Получить
                       //в свойстве ПолноеИмя хранится путь и имя текущего файла
                           Возврат ВычислитьРазмерФайла(ЭтотОбъект.ПолноеИмя)
               КонецПолучить
       КонецСвойства

В этом примере, возможно, вместо свойства можно было бы создать метод ПолучитьРазмерФайла(). Всегда, когда получение значения свойства должно производить вычисления, подумайте над тем, а не сделать ли вместо свойства метод.

В методе Установить Вы можете произвести любые проверки нового значения.

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

  • установить свойству значение по умолчанию
  • игнорировать новое значение
  • вызвать исключение
&ВидноВсем
Свойство Полное ФИО тип Строка
       Поле _ФИО тип Строка = "<>"    //значение по умолчанию
       Установить(НовоеЗначение тип Строка)
               Если НовоеЗначение Это Неопределено
                       //игнорируем значение… не спрашивайте почему :)
                           Возврат
               ИначеЕсли НовоеЗначение=""
                       //используем значение по умолчанию
                       _ФИО ="<>"
               ИначеЕсли НЕ ПроверитьФИО(НовоеЗначение)
                       //вызываем исключение
                           ВызватьИсключение "Не верное ФИО!"
               Иначе
                       //все проверки пройдены – устанавливаем новое значение
                       _ФИО =НовоеЗначение
               КонецЕсли
       КонецУстановить
КонецСвойства

Это синтетический пример, «притянутый за уши», но он демонстрирует все три возможности действий, когда в метод Установить передано не допустимое значение.

Индексируемые свойства

Если поле, связанное со свойством, является индексируемым, или процесс получения/установки значения свойства зависит от параметров, Вы можете добавить дополнительные параметры свойству, которые автоматически добавятся методам Получить и Установить.

Рассмотрим случай, когда поле, связанное со свойством, является массивом:

&ВидноВсем
Свойство Полное ФИОРодителей(Индекс тип Целое) тип Строка
       //сразу создадим массив из 2-х элементов, т.к. у человека 2 родителя
       Поле _ФИОРодителей тип Строка[] = Новый Строка[1]   
       //метод Получить фактически будет иметь дополнительный параметр:
       // Получить(Индекс тип Целое)
       Получить    //дополнительный параметр здесь указывать не надо
               //индекс желательно проверить
               Если Индекс < 0 или Индекс > 1
                           ВызватьИсключение "Не допустимый индекс! Индекс должен быть в диапазоне от 0 до 1."
               КонецЕсли
               //возвращаем значение элемента массива по индексу
              Возврат _ФИОРодителей[Индекс]
       КонецПолучить
       //метод Установить фактически будет иметь дополнительный параметр:
       //Установить(Индекс тип Целое, НовоеЗначение тип Строка)
       Установить(НовоеЗначение тип Строка)    //дополнительный параметр здесь указывать не надо
               //индекс желательно проверить
               Если Индекс < 0 или Индекс > 1
                           ВызватьИсключение "Не допустимый индекс! Индекс должен быть в диапазоне от 0 до 1."
               КонецЕсли
               //устанавливаем значение элемента массива по индексу
               _ФИОРодителей[Индекс] = НовоеЗначение
       КонецУстановить
КонецСвойства

Теперь попробуем обратиться к этому свойству:

Человек.ФИОРодителей(0) = "Иванова Инна Ивановна"
Человек.ФИОРодителей(1) = "Петров Пётр Петрович"
ВыводСтроки Человек.ФИОРодителей(0)    // выводит «Иванова Инна Ивановна»
ВыводСтроки Человек.ФИОРодителей(1)    // выводит «Петров Пётр Петрович»

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

Ограничение доступа на чтение и запись

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

В языке Перфолента для ограничения доступа на чтение и запись используются соответствующие атрибуты ТолькоЧтение и ТолькоЗапись, например:

&ВидноВсем, ТолькоЧтение Свойство Идентификатор тип Целое = ++СчетчикОбъектов 
&ВидноВсем, ТолькоЗапись Свойство Пароль тип Строка 

Для краткого синтаксиса определения свойства атрибуты являются единственным способом установить ограничение на чтение или на запись, а в полном варианте синтаксиса для тех же целей можно определить только один метод, либо метод Получить, либо метод Установить. В полном синтаксисе атрибуты ТолькоЧтение и ТолькоЗапись не обязательны и являются всего лишь способом контроля соответствующего набора методов. Если установлен атрибут ТолькоЧтение, то наличие метода Установить будет ошибкой, аналогично, при установленном атрибуте ТолькоЗапись наличие метода Получить будет ошибкой.

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

&ВидноВсем
Свойство Полное Идентификатор тип Целое
       Поле _Идентификатор тип Целое = ++СчетчикОбъектов 
       Получить
                   Возврат _Идентификатор
       КонецПолучить
КонецСвойства

Теперь определение свойства доступного только для записи:

&ВидноВсем
Свойство Полное Пароль тип Строка
       Поле _Пароль тип Строка = ""
       Установить(НовоеЗначение тип Строка)
                   _Пароль =НовоеЗначение
       КонецУстановить
КонецСвойства

Понятно, что наличие того или иного метода определяет вид доступа на чтение или на запись.

Атрибуты ТолькоЧтение и ТолькоЗапись не совместимы и не могут присутствовать одновременно.

Важно! Есть только 2 способа записать значение в свойство имеющее атрибут ТолькоЧтение.

  • инициализация свойства или поля связанного со свойством
  • установка значения в конструкторе.

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

Создавать свойства доступные только для записи не рекомендуется, в таких случаях лучше использовать методы. Окончательное решение, что использовать, принимать Вам. Например, если у объекта есть свойство ИмяПользователя, то логично иметь и свойство Пароль, а не метод УстановитьПароль. Однако, Вы можете вместо свойств сделать единственный метод УстановитьИмяПользователяИПароль.

Свойства общие для класса

Как и поля, свойства могут быть общими для класса. Что бы сделать свойство общим для класса, достаточно добавить атрибут ОбщийДляКласса. Всё, что было написано об общих для класса полях в статье Конструируем класс. Поля относится и к свойствам.

&ВидноВсем, ОбщийДляКласса Свойство КоличествоОбъектов тип Целое = 0 

Свойства в интерфейсах

В интерфейсах возможен только краткий синтаксис определения свойств. Инициализация значений свойств в интерфейсах не возможна.

Свойства в структурах

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

Структура МояСтруктура
           &ВидноВсем, ОбщийДляКласса Свойство ОбщееСвойство тип Целое = 111     // Правильно. Инициализация допустима.
           &ВидноВсем Свойство ПриватноеПоле1 тип Целое     // Правильно. Нет инициализации.
           &ВидноВсем Свойство ПриватноеПоле2 тип Целое = 222     // ОШИБКА !!! Инициализация НЕ допустима.
КонецСтруктуры

Свойства в модулях

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

Поэтому свойствам, объявленным в модулях (в том числе в модуле Программа), нет необходимости задавать атрибут ОбщийДляКласса, они и так уже общие.

Программа МояПрограмма
           Свойство ВремяСтартаПрограммы тип Дата = ТекущаяДата
КонецПрограммы
Модуль ОбщиеДанные
           &ВидноВсем Свойство ОбщаяСтоимость тип Число = 0
КонецМодуля


Свойства в перечислениях

В перечислениях свойства определять нельзя.

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

Ссылки