Поля и свойства объектов
Функция вместо поля. Вернемся к нашему садовому хозяйству. Вот кому-то снаружи класса понадобился периметр участка. А он его не видит, так как он у нас объявлен Private. Пожалуйста, нам не жалко, сделаем его полем:
Public Периметр As Integer
Теперь он виден всем. И это хорошо. Плохо то, что теперь все, кому не лень, смогут менять наш периметр операторами типа
Участки(k).Периметр = 25
Что же получается? Мы старались, вычисляли периметр по правильной формуле, а тут кто-то раз! – и присвоил ему значение, которое непонятно откуда взялось! Нет, так не годится, вернем все как было:
Private Периметр As Integer
Но теперь недоволен заказчик: ему нужно знать периметр. Что же делать? Нужно устроить так, чтобы периметр был все-таки виден и в то же время защитить его от посягательств. Читать – читайте, а менять извне его значение нельзя!
Как этого добиться? Вот простой способ. Придумаем функцию Периметр_участка, все тело которой состоит из единственного оператора:
Public Function Периметр_участка() As Integer
Return Периметр
End Function
Теперь любой желающий извне может узнать значение периметра таким, например, оператором:
Debug.WriteLine(Участки(k).Периметр_участка)
Поскольку такие обращения к переменной и функции без параметров синтаксически неотличимы, то снаружи могут сколько угодно думать, что обращаются к переменной Периметр_участка, когда на самом деле это функция. И кому какая разница?
А где же здесь желанная цель «только для чтения»? А она достигнута. Потому что присваивать значение функции мы можем только в ее теле, а уж никак не из других модулей. Попробуйте – VB придерется.
На всякий случай напомню, что ответственность за то, чтобы в ячейке Периметр в нужный момент находилось нужное значение, лежит на программисте. Так, в нашем случае, прежде чем дать возможность пользователю со стороны узнать, чему равен периметр, нужно позаботиться, чтобы была выполнена процедура Вычисляем_периметр.
Функция скрывает и обманывает. Поскольку в теле функции мы можем писать какой угодно код, открывается возможность как угодно ограничивать доступ любопытных к значению переменной и даже вводить их в заблуждение:
Public Function Периметр_участка() As Integer
If Периметр > 10000 Then
MsgBox("Периметр участка засекречен")
Return 0
Else
Return Периметр + 1 'Приписка
End If
End Function
Вместо функции – свойство «только для чтения». Идея « Функция вместо поля» настолько хороша, что появилась потребность оформить ее синтаксически.
Сотрите нашу функцию. Создадим ее другим, общепринятым путем. Введите такую строку:
Public Property Периметр_участка As Integer
Нажмите Enter. Перед вами появилась заготовка:
Public Property
Периметр_участка() As Integer
Get
End Get
Set(ByVal Value As Integer)
End Set
End Property
Слово Property означает «свойство». Периметр участка перестал быть просто переменной, перестал быть полем, перестал быть функцией, он стал свойством. Так мы его и будем называть.
Слово Get означает "Получи (узнай) значение свойства", а слово Set означает "Установи (присвой) значение свойству". Наша конструкция состоит из двух частей. Внутрь части Get – End Get мы пишем код, показывающий значение свойства наблюдателю извне. Фактически мы просто переписываем туда тело нашей функции. Внутрь части Set – End Set мы пишем код, позволяющий наблюдателю извне менять значение свойства. Об этой части мы поговорим чуть позже, а сейчас, поскольку мы не собираемся давать возможность изменения значений свойства извне, мы просто стираем эту часть, а в подтверждение своей решимости добавляем в объявление свойства слово ReadOnly («только для чтения»). Вот что у нас получилось:
Public ReadOnly Property Периметр_участка() As Integer
Get
Return Периметр
End Get
End Property
Мы решили поставленную задачу.
Свойства «для чтения-записи». Теперь займемся полями нашего класса, например, полем Длина. Хорошо бы уже при присвоении этому полю значения из текстового поля осуществлялся некоторый контроль значений. Например, мы хотим запретить участки длиннее 500 метров. Однако поле Длина имеет тип Integer и поэтому допускает очень большие числа. Мы можем написать ограничение в коде формы, но это будет нарушением принципа инкапсуляции. Общепринятое средство – создать свойство.
Превратим переменную Длина из поля в модульную переменную, защитив тем самым от воздействия извне:
Private Длина As Integer
Затем создадим свойство Длина_участка:
Public Property Длина_участка() As Integer
Get
Return Длина
End Get
Set(ByVal Value As Integer)
If Value < 500 Then
Длина = Value
Else
MsgBox("Слишком длинный участок")
Длина = 0
End If
End Set
End Property
Мы предоставляем возможность каждому извне узнавать без ограничений значение свойства, поэтому часть Get – End Get у нас заполняется стандартно.
Поговорим о части Set – End Set. Внутренним неприкасаемым хранителем значения длины в нашем классе является модульная переменная Длина. Свойство Длина_участка придумано только для того, чтобы показывать вовне значение этой переменной и по-возможности безопасным и контролируемым образом разрешать извне это значение менять. Часть Set – End Set поэтому имеет своей конечной целью присвоение переменной Длина нужного значения.
Если часть Get – End Get – это функция, то часть Set – End Set – это процедура. Причем с параметром Value. Каждый раз, когда кто-то пытается извне присвоить свойству Длина_участка какое-нибудь значение, это значение приходит в объект-участок в качестве значения параметра Value свойства Длина_участка. Остается только написать в части Set – End Set единственный оператор
Длина = Value
И если бы он остался единственным, то в этом случае мы получили бы свойство для чтения-записи безо всяких ограничений. Но тогда не стоило и огород городить. Ведь поле Длина обеспечивало то же самое. Однако мы помнили о нашем запрете на длинные участки и поэтому часть Set – End Set сделали подлиннее. Теперь каждый, желающий из текстового поля задать длину участка равную 500 и выше, столкнется с сообщением "Слишком длинный участок" и вынужден будет вводить данные вновь.
Обратите внимание, что классу совершенно все равно, что
физически является передатчиком значения извне внутрь свойства. Это может быть текстовое поле, как в нашем случае, или InputBox, или файл. В любом случае значение приходит в объект в качестве значения параметра Value и анализируется согласно коду, написанному в теле свойства программистом.
Итак, какова обычная последовательность создания свойств? Организуется модульная переменная Private – хранительница значения свойства, а затем организуется свойство Property для связи этой переменной с внешним миром.
Если же эта переменная вам внутри класса не нужна, то вы можете обойтись и без нее и вычислять значение свойства прямо в теле свойства в части Get – End Get.
Свойства «только для записи». Не правда ли, звучит странно? Они напоминают писателей, которым нельзя читать. Или фехтующих в потемках.
Предположим, мы не хотим, чтобы снаружи знали, кто является владельцем участка. Вы скажете: Как же так? Ведь тот, кто вписывает имя владельца в текстовое поле на форме, знает это имя! Какой же смысл скрывать? Да, конечно, он знает. С этим ничего не поделаешь. Ну так чтоб хоть другие-то не знали! Если хотят изменить это имя, испортить, – пусть портят, лишь бы не узнали настоящего имени!
Превращаем поле Владелец в модульную переменную:
Private Владелец As String
Вот как выглядит свойство Владелец_участка:
Public WriteOnly
Property Владелец_участка() As String
Set(ByVal Value As String)
Владелец = Value
End Set
End Property
Мы здесь стерли часть Get – End Get и добавили в объявление свойства слово WriteOnly («только для записи»).