Определение классов в программе
От использования
готовых классов .NET Framework мы переходим к определению собственных классов
в программе. Код класса можно разместить в отдельном файле при помощи команды
Project > Add Class, как в VB6, или же просто ввести его в нужном модуле
— например, в стартовом модуле, содержащем точку входа в консольное приложение.
В
процессе тестирования мы предпочитаем связывать каждый класс с процедурой Sub
Main, в которой он используется. Таким образом, код классов не оформляется в
виде отдельных модулей классов, а выделяется в программный модуль с отдельной
процедурой Sub Main, предназначенной для их тестирования. Если вы последуете
нашему примеру, учтите, что код, определяемый на уровне модуля, доступен везде,
где доступен сам модуль. Таким образом, мы создаем некий аналог глобальных переменных
и функций VB .NET — со всеми опасностями, присущими глобальным данным.
VB .NET не
смотрит на то, сколько классов определяется в одном файле. В большинстве классов
определяются один или два конструктора, свойства для чтения и изменения состояния
объекта, а также методы для выполняемых действий. Для примера возьмем простейший
класс Empl oyee с двумя полями (имя и зарплата) и небольшую тестовую программу.
В классе определяются два свойства, доступных только для чтения; эти свойства
возвращают значения полей. Методы в этом классе отсутствуют:
1 Module EmployeeTestl
2 Sub Main()
3 Dim Tom As
New Employee("Tom". 100000)
4 Console.WriteLine(Tom.TheName
& "salary is " & Tom.Salary)
5 Console. ReadLine()
6 End Sub
7 ' Определение
класса
8 Public Class
Employee
9 Private m_Name
As String
10 Private m_Salary
As Decimal
11 Public Sub
New(ByVa1 sName As String.ByVal curSalary As Decimal)
12 m_Name =
Sname
13 m_Salary
= curSalary
14 End Sub
15 Public Readonly
Property TheName()As String
16 Get
17 Return m_Name
18 End Get
19 End Property
20 Public Readonly
Property Salary() As Decimal
21 .Get .
22 Return m_Salary
23 End Get
24 End Property
25 End Class
26 End Module
В строках
2—6 определяется процедура Sub Main, используемая компилятором в качестве точки
входа. Если эта процедура выбрана в качестве стартового объекта (это происходит
по умолчанию, но вообще стартовый объект выбирается в диалоговом
окне Project Properties), она отвечает за создание исходных экземпляров. Далее
созданные объекты обычно создают другие объекты в ответ на получение ими сообщений.
Конечно, в нашей простой программе ничего такого не происходит.
Непосредственное
создание объекта происходит в строке 3, играющей ключевую роль в процессе тестирования
программы. В этой строке при создании нового объекта Empl oyee методу New передаются
два параметра — имя и начальная зарплата. В строке 4 мы выводим значения свойств
TheName и Salагу, чтобы убедиться в том, что исходное состояние созданного объекта
было задано верно.
Класс Empl
oyee определяется в строках 8-25. Как упоминалось выше, для удобства тестирования
код класса определяется в исходном модуле, хотя мы с таким же успехом могли
воспользоваться командой Project > Add Class и выделить его в отдельный файл.
Давайте внимательно
рассмотрим каждую строку в определении класса. В строке 8 ключевое слово Publiс
является атрибутом уровня доступа и определяет, кому разрешено создавать
экземпляры этого класса. В нашем примере класс объявлен открытым, поэтому теоретически
любой желающий сможет создавать его экземпляры после компиляции — для этого
в программу достаточно включить ссылку на сборку, содержащую этот класс (сборки
рассматриваются в главе 13). Чтобы класс мог использоваться только в рамках
нашего проекта и оставался недоступным для внешних программ, ключевое слово
Public следует заменить ключевым словом Friend.
В строках
9 и 10 определяются закрытые поля для хранения информации о состоянии объекта.
В очередной раз напомним, что переменные всегда должны объявляться закрытыми
(Private). В своих определениях классов и модулей мы всегда начинаем имена полей
с префикса m_ или m.
В строках
11-14 определяется конструктор, предназначенный для создания экземпляров класса.
Конструктор задает значения закрытых полей экземпляра в соответствии со значениями
полученных параметров.
В строках
15-19 и 20-24 определяются два открытых свойства, доступных только для чтения.
Они предназначены для получения информации о текущем состоянии объекта. В приведенном
примере использовано ключевое слово Return, однако с таким же успехом можно
было применить старый синтаксис с присваиванием имени свойства:
Get
TheName = m_Name
End Get
Впрочем,
даже в этой форме синтаксис процедуры свойства несколько изменился по сравнению
с VB6 — исчезли старые конструкции Property
Get/Property Set.
Следующая
версия программы показывает, как сделать свойство Salary доступным для чтения
и записи. Для этого достаточно удалить ключевое слово Readonly и добавить небольшой
фрагмент:
Public Property
Salary()As Decimal Get
Return m_Salary
End Get Set(ByVal Value As Decimal)
m_Salary = Value
End Set
End Property
В этом фрагменте
прежде всего следует обратить внимание на чтение нового значения свойства с
применением ключевого слова Value. Иначе говоря, когда в программе встречается
строка вида Tom.Salary = 125000, параметру Value автоматически присваивается
значение 125 000.
Иногда
встречаются ситуации, когда свойство требуется объявить доступным только для
записи. Для этого перед именем свойства ставится ключевое слово WriteOnly, a
затем определяется секция Set без секции Get.
Допустим,
мы решили объявить свойство Salary доступным только для чтения и включить в
класс метод для повышения зарплаты. Метод объявляется как обычная процедура
или функция. В нашем примере метод не возвращает значения, поэтому выбор стоит
остановить на процедуре:
Public Sub RaiseSalary(ByVal
Percent As Decimal)
m_Salary =(1 + Percent) * m_salary
End Sub
Члены класса
объявляются с модификаторами Public, Private или Friend. Ключевое слово Pri
vate означает, что член класса используется только внутри класса.
В
классах иногда объявляются закрытые конструкторы. «Какая польза от закрытого
конструктора?» — спросите вы. Конструктор объявляется закрытым в том случае,
если он вызывается только самим классом исходя из логики его работы.
По
умолчанию в VB .NET для классов и их членов используется уровень доступа Friend
(доступ разрешается только из текущей программы). Впрочем, пропускать атрибуты
уровня доступа при объявлении класса не рекомендуется — особенно если учесть,
что уровень доступа, принятый по умолчанию, не является минимальным.
Атрибуты уровня доступа и создание объектов
Атрибуты
уровня доступа, установленные для класса, управляют возможностью создания объектов
соответствующего типа. Грубо говоря, они являются отдаленным аналогом свойства
Instancing в VB6, хотя для некоторых значений Instancing приходится дополнительно
учитывать уровень доступа конструктора. В табл. 4.5 описано соответствие между
свойством Instancing VB6 и комбинациями атрибутов уровня доступа класса и конструктора.
Таблица
4.5. Значения свойства Instancing и атрибуты уровня доступа
Свойство
Instancing VB6 |
Аналог в
VB. NET |
Private | Класс объявляется с атрибутом Private |
PublicNotCreatable | Класс объявляется с атрибутом Public, но конструктор объявляется с атрибутом Friend |
Singlellse и GlobalSingleUse | Нет аналога в VB .NET |
MultiUse | И класс, и конструктор объявляются с атрибутом Public |
Если класс
используется как шаблон для создания однотипных объектов, программист должен
иметь возможность сослаться на текущий объект, которому принадлежит выполняемый
код. Зарезервированное слово Me всегда интерпретируется как объектная переменная,
обозначающая текущий экземпляр. Применение Me гарантирует, что неоднозначная
конструкция будет интерпретирована в контексте текущего класса.
Также стоит
заметить, что один из самых распространенных (и самых нелепых) примеров использования
Me встречается в ситуациях вроде следующей:
Public Class
Point
Private x As Integer
Private у As Integer
Public Sub New(ByVal
x As Integer.ByVal у As Integer)
Me.x = x
Me.у = у End
Sub
' И т.д.
End Class
Запись Me.
x используется для того, чтобы отличить поле х экземпляра от параметра х, передаваемого
при вызове метода New. Конечно, проблема легко решается добавлением префикса
m_ перед именем переменной класса, однако подобные конструкции часто используются
в С#; возможно, они встретятся в программе, сопровождение которой вам будет
поручено.
Метод RaiseSalary
класса Employee можно сделать и поинтереснее. Предположим, повышения зарплаты
до 10% происходят автоматически, но для больших сумм требуется специальный пароль.
В прежних версиях VB такие задачи решались при помощи необязательных параметров.
Хотя эта возможность сохранилась и в VB .NET, существует более изящное решение
с определением двух версий RaiseSalary. Используя возможность перегрузки
методов, мы определяем два разных метода для разных случаев.
В VB .NET
синтаксис перегрузки методов очень прост: для этого в программе просто определяются
два метода с одинаковыми именами и разными параметрами. Тем не менее мы настоятельно
рекомендуем использовать ключевое слово Over! oads. По нему пользователи вашего
кода узнают о том, что метод перегружается намеренно, а не в результате ошибки.
В следующем фрагменте приведены две версии метода RaiseSalary, о которых говорилось
выше:
Public Overloads Sub RaiseSalary(ByVal Percent As Decimal)
If Percent >
0.1 Then
' Операция запрещена - необходим пароль
Console.WhteLineC'MUST
HAVE PASSWORD TO RAISE SALARY " & _
"MORE THAN
10*!!!!") Else X
m_Salary =(1
+ Percent) * m_salary End If End Sub
Public Overloads
Sub RaiseSalary(ByVal
Percent As Decimal._
ByVal Password
As Stqng)
If Password
-"special Then
m_Salary = (1
+ Percent) * m_Salary
End If End Sub
При
перегрузке методы класса различаются только по типам параметров. Методы не могут
перегружаться по типу возвращаемого значения или уровню доступа.
Ниже приведен
пример класса Empl oyee с перегруженным методом Rai seSalany, а также небольшая
тестовая программа. Обратите внимание: 10%-ный порог не кодируется в программе,
а определяется в виде константы:
Option Strict
On Module Modulel Sub Main()
Dim Tom As New
Employee("Tom". 100000)
Console.WhteLineCTom.TheName
& " has salary " & Tom.Salary)
Tom.RaiseSalary(0.2D)
' Суффикс D -
признак типа Decimal
Console.WriteLine(Tom.TheName
& " still has salary " & Tom.Salary)
Console. WhteLine()
Dim Sally As
New Employee("Sally", 150000)
Console.WriteLine(Sally.TheName
& " has salary " & Sally.Salary)
Sally.RaiseSalary(0.2D,"special")
' Суффикс D
- признак типа Decimal
Console.WriteLine(Sally.TheName
& "has salary "SSally.Salary)
Console. WriteLine()
Console.WriteLine("Please
press the Enter key")
Console. ReadLine()
End Sub
End Module
Public Class
Employee
Private m_Name As String
Private m_Salary As Decimal
Private Const
LIMIT As Decimal = 0.1D
Public Sub New(ByVal theName As String,ByVal curSalary As Decimal)
m_Name = thename
m_Salary = curSalary
End Sub
Readonly Property
TheName()As String Get
Return m_Name
End Get '
End Property
Readonly Property
Salary()As Decimal Get
Return m_Salary
End Get
End Property
Public Overloads
Sub RaiseSalary(ByVal Percent As Decimal)
If Percent >
LIMIT Then
' Операция запрещена - необходим пароль
Console.WriteLine("MUST HAVE PASSWORD TO RAISE SALARY " & _
"MORE THAN
LIMIT!!!!")
Else
m_Salary =(1
+Percent)*m_salary End If End Sub
Public Overloads
Sub RaiseSalary(ByVal Percent As Decimal._
ByVal Password As String)
If Password
= "special" Then
m_Salary =(1 + Percent) * m_Salary
End If
End Sub
End Class
Если в вашем
классе не определен конструктор, VB .NET автоматически генерирует для него конструктор,
вызываемый без аргументов. Работа этого конструктора сводится к инициализации
всех полей экземпляра значениями по умолчанию. Такой конструктор называется
конструктором по умолчанию или безаргумеитнъм конструктором. Если
в классе определен хотя бы один пользовательский конструктор, VB .NET не станет
генерировать конструктор по умолчанию.
Ничто не
мешает вам определить в классе несколько конструкторов с разными уровнями доступа.
Например, можно определить абсолютно безопасный конструктор с атрибутом Public
и конструктор с атрибутом Friend, использование которого сопряжено с чуть большим
риском. Конечно, эти конструкторы должны вызываться с разными параметрами, поскольку
VB .NET различает методы по списку параметров, а не по модификаторам уровня
доступа.
Конструкторы
перегружаются, как и остальные методы, однако при этом нельзя использовать ключевое
слово Overloads. Ниже приведен фрагмент обновленной версии класса Employee с
конструктором, позволяющим задать значение нового поля.
Public Class
Employee
Private m_Name
As String
Private m_NickName
As String
Private m_Salary
As Decimal
Public Sub NewCByVal
sName As String.ByVal curSalary As Decimal)
m_Name = sName
m_Salary = curSalary
End Sub
Public SubNewCByVal
theName As String.ByVal nickName As String._
ByVal curSalary
As Decimal)
m_Name = theName
m_NickName =
nickName
m_Salary = curSalary
End Sub
Компилятор
выбирает вторую версию конструктора лишь в том случае, если при вызове передаются
два строковых параметра и один числовой. При передаче одной строки и числа выбирается
первый конструктор.
Перегрузка
конструкторов приводит к дублированию кода в программе. Так, в приведенном выше
фрагменте значения m_Name и fli_Sa,lary присваивались в обоих конструкторах.
В VB .NET для таких ситуаций предусмотрена специальная сокращенная
запись: конструкция MyClass.New вызывает другой конструктор класса [ На момент
написания книги также можно было воспользоваться ключевым словом Me, но вариант
с MyClass является предпочтительным. ]. Пример:
Public Sub New(ByVal
sName As String.ByVal curSalary As Decimal)
m_Name = Sname
mJSalary = curSalary
End Sub
Public Sub New(ByVal
sName As String, ByVal nickName As String._ ByVal curSalary As Decimal)
MyClass.Newt
sName.curSalary)
m_NickName =nickName
End Sub
При вызове
другого конструктора конструкцией MyClass. New порядок определения конструкторов
в программе не важен. VB .NET выбирает конструктор по типу переданных параметров
независимо от его места в определении класса.
Помните,
что MyClass — ключевое слово, а не объект. Значение MyCLass нельзя присвоить
переменной, передать процедуре или использовать в операторе Is. В подобных ситуациях
используется ключевое слово Me; оно обозначает конкретный объект, код которого
выполняется в настоящий момент.