Абстракция
Абстракцией называется моделирование объектов в программе. Другими словами, речь идет об имитации реально существующих объектов, отражающей особенности их взаимодействия в окружающем мире. Так, первый объектно-ориентированный язык Simula (http://java.sun.com/people/jag/SimulaHistory.html) разрабатывался специально для задач имитации и моделирования. Впрочем, модные концепции виртуальной реальности выводят принцип абстракции на совершенно новый уровень, не связанный с физическими объектами. Абстракция необходима, потому что успешное использование ООП возможно лишь в том случае, если вы сможете выделить содержательные аспекты своей проблемы.
Приступая к построению объектной модели, всегда задавайте себе вопрос: какие свойства и методы должны входить в объект, чтобы он адекватно моделировал ситуацию для решения поставленной задачи?
ArrayList
Класс ArrayList реализует динамический массив, размеры которого автоматически увеличиваются и уменьшаются по мере надобности. Динамические массивы работают чуть медленнее обычных массивов, но они заметно упрощают многие задачи программирования. Кроме того, в отличие от большинства массивов класс ArrayLi st является гетерогенным, то есть позволяет хранить объекты разных типов. В главе 5 будет показано, как создать класс ArrayList для хранения объектов лишь одного типа; вы также узнаете о некоторых нюансах, связанных с хранением обобщенных объектов в ArrayLi St.
Использование ArrayList вместо базового массива означает, что вам не придется часто вызывать ReDim Preserve для сохранения существующих данных. Достаточно вызвать метод Add, и класс ArrayList сам выполнит всю черновую работу. Класс ArrayList содержит ряд других полезных методов. Например, метод AddRange позволяет перенести в динамический массив все содержимое существующего массива всего одной командой. После завершения обработки элементы можно скопировать обратно. В частности, это позволяет легко объединить содержимое двух массивов. В табл. 4.3 перечислены основные члены класса ArrayList (полный список приведен в электронной документации).
Рисунок 4.6 . Метод GetDirectories в справочной системе
Атрибуты уровня доступа и создание объектов
Атрибуты уровня доступа, установленные для класса, управляют возможностью создания объектов соответствующего типа. Грубо говоря, они являются отдаленным аналогом свойства Instancing в VB6, хотя для некоторых значений Instancing приходится дополнительно учитывать уровень доступа конструктора. В табл. 4.5 описано соответствие между свойством Instancing VB6 и комбинациями атрибутов уровня доступа класса и конструктора.
Две разновидности циклических ссылок Структурные типы
Традиционно в объектно-ориентированных языках возникало немало проблем с простейшими типами данных — такими, как обычные целые числа. Дело в том, что в объектно-ориентированном языке все данные должны быть объектами. С другой стороны, создание объекта сопряжено с определенными затратами на выполнение служебных операций (таких, как выделение блока памяти для объекта). Обработка сообщения «сложить» также уступает по скорости простой математической операции сложения и т. д. Стоит добавить, что в языках с автоматической сборкой мусора некоторое время расходуется на уничтожение неиспользуемых объектов.
Ранние объектно-ориентированные языки пошли по самому прямолинейному пути. Скажем, в Smalltalk все данные интерпретировались как объекты. В результате такие языки работали медленнее, чем языки с разделением примитивных типов и объектов. С другой стороны, подобное разделение приводило к усложнению программ, поскольку программный код, работавший с числами, приходилось отделять от кода, работавшего с объектами. Чтобы интерпретировать число в объектном контексте, его приходилось «заворачивать» в объект. Например, в Java сохранение числа в эквиваленте динамического массива выглядело примерно так:
anArrayList.Add(Integer(5));
Число 5 «заворачивалось» в объект Integer. Такие программы плохо читались и медленно работали.
В .NET Framework были объединены лучшие стороны обоих решений. В общем случае числа интерпретируются как примитивные типы, но при "необходимости они автоматически интерпретируются как объекты. Таким образом, для обычного числового литерала можно вызвать метод или занести его в хэш-таблицу без дополнительных усилий. Это называется автоматической упаковкой (boxing); обратный процесс называется распаковкой (unboxing).
Для нас, программистов, из этого вытекает важное следствие: хотя в VB .NET все данные являются объектами, не каждая переменная в программе содержит манипулятор блока памяти и создается оператором New. Разумеется, ничто не дается даром: программисту приходится помнить о различиях между структурными и ссылочными типами. Первое, наиболее очевидное различие заключается в том, что новые экземпляры структурных типов создаются без ключевого слова New. Вам не придется (да и не удастся) использовать конструкции вида Dim a As New Integer(5).
Более серьезное различие связано с передачей переменных процедурам по значению. Как было сказано выше, при передаче изменяемого объекта по значению процедура может изменить состояние объекта. Переданные по значению структурные типы ведут себя вполне традиционно — все изменения теряются при выходе из вызванной процедуры или функции (иногда это называется структурной семантикой в отличие от ссылочной семантики).
Все числовые типы VB .NET являются структурными типами; к этой же категории относится и такой тип, как дата. Как будет показано ниже, VB .NET позволяет определить пользовательские структурные типы, если по соображениям быстродействия вы хотите свести к минимуму затраты на работу с объектами или же предпочитаете работать с объектами, обладающими структурной семантикой.
Чтобы узнать, обладает ли некий тип структурной семантикой, передайте переменную этого типа следующей функции.
Function IsValueType(ByVal foo As Object)As Boolean
If TypeOf (foo)Is System.ValueType Then
Return True Else
Return False
End If
End Function
Для объектов структурного типа оператор Equal s всегда возвращает True, если структурные объекты содержат одинаковые данные. Синтаксис вызова выглядит так:
a..Fquals(b)
Учтите, что для ссылочных типов этот принцип в общем случае не выполняется. Например, два массива могут содержать одинаковые элементы, но равными при этом они не считаются.
В VB .NET структурные типы делятся на две категории: структуры и перечисляемые типы. Мы начнем с перечисляемых типов, а затем перейдем к структурам, которые представляют собой «облегченные» варианты объектов.
Хэш-таблицы
Простые и динамические массивы удобны прежде всего тем, что вы можете напрямую обратиться к любому элементу по индексу. Конечно, для этого необходимо знать индекс. В следующей структуре данных — хэш-таблице — произвольный доступ к данным осуществляется по ключу. Допустим, у вас имеется хэш-таблица с именем theData. Команда theData("Bill 's Address") позволяет извлечь из хэш-таблицы нужный элемент без циклического перебора всего содержимого. Хэш-таблицы очень удобны в ситуациях, когда вы хотите получить быстрый доступ к значению по связанному с ним уникальному атрибуту, то есть ключу. Разумеется, программирование хэш-таблицы — задача непростая [ Для этого необходимо построить хорошую функцию хэширования для вычисления индекса данных по ключу, а также решить неизбежную проблему коллизий, то есть совпадения хэш-кодов у двух разных элементов. Даже терминология выглядит устрашающе... ], но, к счастью, эта работа уже выполнена за вас разработчиками .NET Framework.
Другую категорию структур данных, предназначенных для выборки значения по ключу, составляют ассоциативные массивы (словари). Они часто реализуются в виде хэш-таблиц с дополнительным кодом для выполнения особых операций (например, обнаружения повторяющихся значений или ключей).
В табл. 4.4 перечислены важнейшие методы класса Hashtable (за полным списком обращайтесь к электронной документации).
Методы класса HashTable учитывают регистр символов в строковых ключах, и действие команды Option Compare Text на них не распространяется. О том, как создать хэш-таб-лицу, игнорирующую регистр символов, рассказано в главе 5.
Хронометраж — насколько быстрее работает класс StringBuilder?
Хотя Microsoft не разрешает публиковать точные результаты хронометража для бета-версий (и это вполне разумно, поскольку в них содержится большой объем отладочного кода), отношение результатов, полученных в ходе измерений, почти всегда остается более или менее постоянным. Иногда в окончательной версии это отношение слегка изменяется, но в гораздо меньшей степени, чем абсолютные значения показателей.
Хронометраж в VB .NET реализуется легко — достаточно объединить метод Now с методом Ticks класса DateTlme. Как подсказывает само название, метод Now возвращает текущие показания системных часов. Метод Ti cks возвращает число типа Long, равное количеству 100-наносекундных интервалов, прошедших с 00:00 1 января 0001 года (1 наносекунда = 1/1 000 000 000 секунды).
Следующая программа использовалась для оценки того, насколько быстрее класс StringBuilder выполняет присоединение символов в конец строки по сравнению с классом String. Выигрыш растет с увеличением количества символов; при 50 000 символов эффективность возрастала более чем в 800 раз!
Option Strict On Module Modulel
Sub Main()
Dim i As Integer
Dim StartTime As New DateTime()
Dim EndTime As New DateTime()
StartTime =DateTime.Now()
Dim theText As New System.Text.SthngBuilder()
For i =1 To 50000
theText =theText.Append("A")
Next
EndTime =DateTime.Now
Dim answerl,answer2 As Long
' Количество 100-наносекундных интервалов
answer1 =EndTi me.Ticks()-StartTime.Ticks()
StartTime =DateTime.Now()
Dim aString As String
For i =1 To 50000
aString =aString & "A"
Next
EndTime =DateTime.Now
' Количество 100-наносекундных интервалов
answer2 =(EndTime.Ticks()-StartTime.Ticks())
Console.WriteLine("StringBuilder was " & _ answer? /answerl & "times faster.")
Console.ReadLine()
End Sub
End Module
Импортирование
Обращаясь к хорошим знакомым, мы не называем их по имени-отчеству и фамилии. В .NET предусмотрена возможность импортирования пространств имен командой Imports, что позволяет избавиться от громоздких полных имен. После правильной команды Imports все ссылки вида
System.Text.StringBuilder .
могут заменяться простым именем StringBuilder.
Пространство имен System автоматически импортируется в каждое решение, созданное в Visual Studio. Благодаря этому методы этого пространства имен могут вызываться в виде Console.WriteLine() вместо полного имени System.Console.WrlteLlne().
Список пространств имен, автоматически импортируемых в решение, находится на странице Imports окна свойств решения (Рисунок 4.2).
Загрузите в Object Brewser автоматически импортируемое пространство имен Microsoft. Visual Basic — вы увидите, что оно содержит различные функции, существовавшие в Visual Basic и сохраненные в VB .NET (Рисунок 4.3).
Импортирование пространства имен Microsoft. Visual Basic. Constants позволяет использовать старые константы VB — такие, как vbCrtf. .NET-версии многих констант не имеют префикса vb (например, CrLf) и находятся в пространстве имен
Microsoft.VIsualBasi с.Control Chars.
Команда Imports должна располагаться перед всеми остальными объявлениями, включая объявление имени модуля, но после директив Option (таких, как Option Strict On или Option Compare Text).
Инкапсуляция
В ООП термин «инкапсуляция» означает то, что мы обычно называем маскировкой данных. Скрывая данные, вы определяете свойства и методы для работы с ними. Вспомните, что говорилось выше, — успешное применение ООП возможно лишь в том случае, если все операции с внутренними данными объекта осуществляются посредством обмена сообщениями. Данные объекта хранятся в полях экземпляра; также часто встречается термин «переменные экземпляра». В сущности, это одно и то же, и выбор зависит в основном от того, к какому термину вы привыкли; в этой книге обычно используется термин «поля экземпляра». Текущее состояние объекта определяется текущими значениями полей экземпляра. Не забывайте главное правило: никогда не предоставляйте прямой доступ извне к полям экземпляра (внутренним данным объекта).
Вернемся к примеру с объектно-ориентированной программой для отдела кадров, в которой мы определили класс Employee. В переменных класса Еmplоуее могут храниться следующие сведения:
Дата приема на работу.
Текущая зарплата.
Чтобы изменить значения полей экземпляра, пользователи не обращаются к ним напрямую, а изменяют свойства и вызывают методы типа Rai seSalаrу. Разумеется, метод RaiseSalary будет изменять поле с текущей зарплатой, но в нетривиальном классе Employee он может работать с несколькими полями. Например, легко представить себе метод Rai seSalагу, который принимает решение о повышении зарплаты с учетом ее текущего уровня, рабочего стажа и личных достижений работника. Подведем итог. Инкапсуляция определяет функциональность объекта с точки зрения пользователя. Ее непосредственными проявлениями в VB .NETвыступают члены класса (методы, события и свойства).
Рискуя надоесть частыми повторениями, мы все же еще раз подчеркнем: успешное применение инкапсуляции возможно, если другие части вашей программы никогда не получают прямого доступа к полям экземпляра (переменным) ваших классов. Программа должна взаимодействовать с ними только через вспомогательные члены класса. Только при наличии закрытых данных, недоступных извне, объект превращается в «черный ящик» с четкими правилами поведения и неизвестным внутренним устройством. Ограничение доступа к данным имеет определяющее значение как для повторного использования, так и для надежности объекта при долгосрочном использовании.
Is и Nothing
Оператор Is проверяет, ссылаются ли две объектные переменные на одну область памяти. Следующий фрагмент в обоих случаях выводит True, поскольку в результате операций присваивания все объектные переменные ссылаются на одну область памяти:
Dim Objectl As New Object()
Dim ObjectZ As New Object()
Dim Objects As New Object()
ObjectZ =Object1
Objects Object2
Console.WriteLine(Objectl Is Object2)
Console.WriteLine(Object1 Is Object3)
Как и в прежних версиях VB, присваивание объектной переменной значения Nothi ng разрывает ее связь с блоком памяти. Когда объектная переменная равна Nothing, она не ассоциируется ни с каким объектом. В этом состоянии находятся все объектные переменные, которые были объявлены в программе, но еще не инициализировались. В программе часто встречаются проверки следующего вида:
If anObject Is Nothing Then
' Переменная не связана с объектом, присвоить значение
Else
' Значение было присвоено ранее
End If
Дополнительная информация о том, что происходит при присваивании объектным переменным значения Nothing, приведена в разделе «Сборка мусора и завершение».
Экземпляры
Итак, вы решили, какие классы должны входить в ваш проект. На следующем этапе построения объектной модели рассматриваются конкретные экземпляры этих классов. Попробуйте ответить на три вопроса:
-
Каким состоянием должен обладать объект?
Какими отличительными особенностями должен обладать объект?
Каким должно быть поведение объекта?
В полях экземпляра хранится информация об операциях, выполняемых объектом; некоторые поля могут использоваться для хранения информации об истории существования объекта. В совокупности эти сведения определяют состояние объекта. Состояние объекта подвержено частым изменениям, но, как было сказано выше, любые изменения в состоянии объекта в результате внешних воздействий должны происходить только в результате полученных сообщений.
Текущее состояние объекта не обеспечивает его однозначной идентификации. Два объекта могут находиться в одинаковом состоянии, одинаково реагировать на внешние воздействия, но при этом они все равно остаются разными объектами (как два массива с одинаковым содержимым). Таким образом, должен существовать некий критерий, по которому объект можно отличить от других похожих объектов. К поведению объекта относится информация о том, что он делает в данный момент и что он теоретически может сделать в будущем. В VB .NET поведение объекта определяется его свойствами, методами и событиями.
Несомненно, эти три характеристики влияют друг на друга, и это обстоятельство должно учитываться в коде класса. Например, поведение объекта зависит от его текущего состояния: заблокированное текстовое поле ведет себя совсем не так, как доступное, и об этом следует помнить при проектировании класса.
Как объекты взаимодействуют друг с другом?
Согласно одному из ключевых принципов ООП каждый класс (= шаблон для построения объектов) отвечает за выполнение небольшой группы взаимосвязанных задач. Проектирование и отладка специализированного класса, предназначенного для создания простых объектов с небольшим количеством задач, занимает гораздо меньше времени, чем разработка классов со сложными внутренними структурами данных, многочисленными методами и свойствами. Если потребуется сделать нечто такое, на что ваш объект не рассчитан, не стоит вносить изменения в исходный класс и усложнять его — лучше определите новый класс, оптимизированный для решения этой задачи. Предоставьте старому объекту доступ к объектам нового типа, и старый объект сможет обратиться к новому объекту с запросом на выполнение нужной операции.
Пользователи прежних версий VB хорошо знакомы с подобным взаимодействием между объектами. В частности, именно это и происходит при вызове методов или задании свойств элементов. С точки зрения пользователя, запрос к другому объекту реализуется обращением к свойству или вызовом метода этого объекта. На внутреннем уровне в обработке запросов участвуют обобщенные версии функций и процедур VB .NET (см. главу 3). Таким образом, все подробности внутренней реализации остаются скрытыми от внешнего мира.
На жаргоне ООП этот принцип формулируется так: объекты взаимодействуют с другими объектами только посредством обмена сообщениями. Еще раз подчеркнем — объект никогда не должен напрямую работать с внутренними данными другого объекта. Все взаимодействие должно происходить на уровне сообщений (задания свойств и вызова методов). Проектируйте свои объекты по принципу «черного ящика»: объект реагирует на сообщения из определенного набора, а его фактическая реализация остается неизвестной.
Подведем итог. Все операции с объектами в программе должны сводиться к изменению их свойств и вызову методов. Не используйте открытые (глобальные) переменные в классах или объектах, созданных на их основе.
Содержание
|
Вперед
|
Классы как пользовательские типы
Класс также можно рассматривать как «интеллектуальный» пользовательский тип, обладающий расширенными возможностями — например, перед изменением своих внутренних данных класс может проверить их новые значения. При обращении к данным класс может проверить полномочия стороны, от которой поступил вызов. Наконец, представьте себе пользовательский тип, который возвращает данные в определенном формате вместо того, чтобы просто выдавать их внутреннее представление.
С этой точки зрения объект представляет собой переменную пользовательского типа, с которой ассоциированы функции обращения к данным и их проверки. Главный принцип работы с данными класса — замена прямого доступа к данным вызовом вспомогательных функций.
На первый взгляд организация доступа к данным при помощи функций только прибавляет работы программисту, но это небольшое усложнение с лихвой компенсируется преимуществами от проверки и контроля доступа к данным.
По мере освоения ООП перед вами откроются новые возможности, не ограничивающиеся простой проверкой или возвращением внутренних данных объекта.
Классы коллекций в .NET Framework
Чтобы пробудить в вас интерес к .NET Framework, мы кратко рассмотрим некоторые классы коллекций. В этих классах реализуются стандартные структуры данных, часто используемые в нетривиальных программах. Коллекции настолько важны, что они по умолчанию автоматически импортируются в каждое решение VB .NET (в пространстве имен System.Collections).
Me
Если класс используется как шаблон для создания однотипных объектов, программист должен иметь возможность сослаться на текущий объект, которому принадлежит выполняемый код. Зарезервированное слово 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_ перед именем переменной класса, однако подобные конструкции часто используются в С#; возможно, они встретятся в программе, сопровождение которой вам будет поручено.
Наследование
В качестве примера наследования представьте себе классы для отдельных категорий работников (класс Programmer, класс Manager и т. д.). Механизм, используемый для создания таких классов на базе класса Empl oyee, называется наследованием. В иерархии наследования класс Employee называется базовым, а класс Programmer — производным классом. Производные классы:
содержат все члены базового класса (хотя поведение этих членов может быть совершенно иным).
Например, метод RaiseSalary класса Manager может давать менеджеру большую прибавку к жалованию, чем метод RaiseSalary класса Programmer, при одинаковом стаже и личных показателях.
Производный класс может содержать новые методы, не имеющие аналогов в базовом классе.
Например, в класс Manager может быть включено новое свойство Secretary.
Разработчики давно хотели видеть наследование в VB и громко жаловались на его отсутствие. Нельзя сказать, что шум был поднят на пустом месте, однако многие склонны переоценивать важность наследования. Дело в том, что наследование, если хорошенько разобраться, всего лишь избавляет программиста от необходимости заново писать готовый код. В наследовании нет никакой мистики — это лишь способ упростить повторное использование программного кода. В нашем примере классы Employee и Manager обладали рядом сходных черт (наличие даты найма, зарплаты и т. д.). Зачем программировать свойство Salary в двух местах, если код будет абсолютно одинаковым? При полноценной реализации наследования использование функциональности базового класса в производном классе практически не требует дополнительных усилий — производный класс изначально наследует все члены своего предка. Программист может переопределить некоторые члены базового класса в соответствии со спецификой производного класса. Например, если менеджер автоматически получает 8-процентную прибавку к зарплате, тогда как для большинства работников прибавка составляет всего 4%, метод RaiseSalагу класса Manager должен заменить метод RaiseSalary базового класса Employee. С другой стороны, методы вроде GetName в изменении не нуждаются и остаются в прежнем виде.
Многие влиятельные теоретики ООП полагают, что от наследования вообще стоит дер-жаться подальше, и рекомендуют заменить его использованием интерфейсов (конечно, в VB .NET поддерживаются обе возможности). Такое отношение связано с проблемой неустойчивости базовых классов, которая в VB .NET отошла на второй план (за подробностями обращайтесь к главе 5). Использование интерфейсов вместо классического наследования иногда называется наследованием интерфейсов, тогда как за классическим наследованием закреплен термин «наследование реализации».
Напоследок мы хотим предупредить: не используйте наследование, если вы твердо не уверены в существовании логической связи «является частным случаем». Например, не создавайте класс Contractor (внештатный работник), производный от Employee, только для того, чтобы избавиться от хлопот по дублированию кода свойств имени или номера социального страхования. Внештатный работник не является служащим компании, и бухгалтер, простоты ради оформивший его по общим правилам, только наживет себе неприятности с налоговой инспекцией. То же относится и к вам: применение наследования при отсутствии логической связи «является частным случаем» приведет к печальным последствиям (см. главу 5).
Объектные переменные
Рассмотрим следующий фрагмент:
Dim thing As New Object
Dim aRandomlnstance As New Random
В нем объявляются и создаются две переменные: thing и aRandomlnstance. Первая переменная содержит ссылку на тип Object, а вторая — ссылку на экземпляр класса Random. Следующая команда вполне допустима даже в режиме жесткой проверки типов (Option Strict On), поскольку в VB .NET все переменные в конечном счете представляют собой объекты:
thing = aRandomlnstance
С другой стороны, обратное присваивание (aRandomlnstance = thing) недопустимо, поскольку не каждый объект является экземпляром класса Random.
Объектную переменную можно рассматривать как манипулятор блока памяти (причем не фиксированного, а перемещаемого). Объектные переменные также часто называют ссылками (references) или интеллектуальными указателями (smart pointers). Обычно при использовании знака = с ключевым словом New манипулятор связывается с блоком памяти, в котором хранится соответствующий объект (при работе с так называемыми структурными типами возникают некоторые тонкости, которые будут рассматриваться далее в этой главе).
Как будет показано в следующей главе, общим предком всех типов VB .NET является тип Object. Именно поэтому в VB .NET любую величину можно сохранить в переменной типа Object, а любой созданный объект поддерживает методы класса Object. Например, поскольку в классе Object определен метод ToString, каждый класс позволяет получить строковое представление объекта (полезность которого зависит от реализации). Метод ToString автоматически вызывается при использовании конструкций вида Console. WriteLine(foo).
Если объектная переменная содержит манипулятор блока памяти, в результате операции присваивания второй объектной переменной будет присвоен манипулятор того же блока памяти. Но если вы забудете о том, что для работы с одним блоком памяти используются две разные переменные, это может привести к печальным последствиям — изменения в состоянии объекта, внесенные через одну переменную, автоматически повлияют на другую переменную. Для примера рассмотрим следующий фрагмент:
Sub Maln()
Dim A As New ArrayList()
Dim В As ArrayList
В = А
B.Add("foo")
Console.WriteLine(A.Count)
Console.ReadLine() End Sub
Динамический массив А также будет содержать строку foo, поэтому выведенное значение A.Count будет равно 1.
Если вы знакомы с языками, в которых широко используются указатели (например, С или Pascal), вы увидите, что у объектных переменных есть много общего с указателями. Главное различие состоит в том, что разыменование (dereferencing) объектных переменных происходит автоматически и с ними не могут выполняться математические операции.
Поскольку в VB .NET строки и массивы являются объектами, следует помнить, что для работы с ними используются объектные переменные. Как было показано в главе 3, это позволяет использовать встроенные возможности соответствующих классов при помощи синтаксиса «.». Например, при работе с массивом через переменную апАггау команда anArray.Sort() отсортирует массив чрезвычайно эффективным методом быстрой сортировки.
К сожалению, за все хорошее приходится платить. Передача объектных переменных по значению связана с определенными трудностями, которые теперь распространяются и на стандартные объекты вроде массивов. Данная тема рассматривается в. разделе «Проблемы с передачей объектных переменных по значению» этой главы.
Как и в прежних версиях VB, объектные переменные могут использоваться для получения более компактной записи. Например, в следующем фрагменте определяется короткое имя аВох, которое будет использоваться вместо длинного Му-
Form.TextBoxl:
Dim aBox As System.Windows.Forms.TextBox aBox = MyForm.TextBoxl
Подобные сокращения часто используются в сочетании с ключевым словом With:
With aBox
.AutoSize =False
.Height =1000
.Width =200
.Text ="Hello"
End With
Область видимости переменных
Переменные класса (в том числе и закрытые поля), объявленные за пределами его методов или свойств, доступны для всех членов класса. Переменные, объявленные в методе или свойстве, являются локальными по отношению к этому методу или свойству.
Переменные, объявленные на уровне модуля, доступны для всех классов, определенных в этом модуле, и для всего кода, обладающего доступом к этому модулю.
Таким образом, переменные уровня модуля являются глобальными по отношению к экземплярам классов. Пример:
Module Modulel
Dim aGlobal As Integer = 37
Sub Main()
Dim anA As New А()
Dim aB As New B()
Console. ReadLine()
End Sub
Public Class A Sub New()
aGlobal =aGlobal +17 Console.WriteLine(aGlobal)
End Sub
End Class
Public Class В Sub New()
Console.WriteLine(aGlobal)
End Sub
End Class
End Module
В данном случае целая переменная aGlobal определяется на уровне модуля, поэтому изменения, вносимые в aGlobal классом А, будут восприняты классом В. Использовать переменные уровня модуля не рекомендуется — все взаимодействие между классами должно быть реализовано на уровне обмена сообщениями!
В прежних версиях VB широко практиковалось хранение общих данных классов в глобальных переменных. В VB .NET надобность в этом небезопасном приеме отпала. За дополнительной информацией обращайтесь к разделу «Общие данные в классах» этой главы.
Общие члены классов
Закрытые общие поля классов в сочетании со ReadOnly-свойствами очень удобны, но этим область применения ключевого слова Shared не исчерпывается. В классе можно объявлять общие свойства и методы. Как было показано на примере класса Math, при обращении к общим средствам класса указывается либо имя класса, либо имя конкретного экземпляра. Допустим, в класс Employee включается общая функция Calcul ateFICA, зависящая от двух открытых констант:
Public Const FICA_LIMIT As Integer = 76200
Public Const FICA_PERCENTAGE As Decimal = 0.062D
Функция CalculateFICA выглядит так:
Public Shared Function CalculateFICA(ByVal aSalary As Decimal) As Decimal
If aSalary > FICA_LIMIT Then
Return FICA_LIMIT * FICA_PERCENTAGE
Else
Return aSalary * FICA_PERCENTAGE
End If
End Function
Общие члены класса могут использоваться без создания экземпляров Empl oyee, только по имени класса. Пример:
System.Console.WriteLine(Employee.
CalculateFICA(100000))
С другой стороны, метод мджно вызвать и для конкретного экземпляра Employee:
System.Console.WriteLine
(Tom.CalculateFICA
(Tom.GetSalary())
Конструкторы тоже можно объявлять общими, для этого в объявление метода
New включается ключевое слово Shared. Общие конструкторы:
могут работать только с общими полями класса. Как правило, общие конструкторы применяются только для инициализации общих данных. Код общего конструктора выполняется при создании первого экземпляра указанного класса, перед вызовом всех остальных конструкторов.
Общие данные в классах
Вернемся к классу Еmploуее. Допустим, каждому работнику необходимо присвоить уникальный номер. В старых версиях VB задача решалась при помощи глобальных переменных, что приводило к нарушению инкапсуляции и создавало потенциальную угрозу случайного изменения номеров внешним кодом. Логика подсказывает, что номер должен увеличиваться только при создании нового объекта Empl оуее.
В VB .NET наконец-то появились средства для достижения этой цели. Идея проста: в классе определяются данные, совместно используемые всеми экземплярами данного класса, однако внешний доступ к этим данным находится под вашим полным контролем (например, через обращение к свойству). Не стоит и говорить, что эти поля никогда не должны объявляться открытыми...
Такие поля называются общими (shared). Они идеально подходят для таких ситуаций, как в нашем призере с присвоением последовательных номеров. В классах также могут определяться общие свойства и методы. Недостаток заключается в том, что общие члены классов не могут работать с обычными полями, свойствами или методами. Иначе говоря, общие члены работают только с другими общими членами. Дело в том, что общие данные существуют еще до создания объекта, поэтому было бы нелогично разрешать общим членам доступ к конкретным объектам.
Ниже приведен фрагмент новой версии класса Employee с использованием общих данных для присвоения номеров. В классе определяется закрытая общая переменная типа Integer, которая:
ассоциируется со ReadOnly-свойством, возвращающим ее текущее значение;
изменяется (увеличивается) только в конструкторе класса.
В совокупности это означает, что работнику никогда не будет присвоен номер 0 и что новый номер выделяется только при создании нового объекта Empl oyee — именно это нам и требовалось:
Public Class Employee
Private m_Name As String
Private m_Salary As Decimal
Private Shared m_EmployeeID As Integer = 1
Public Sub New(ByVal theName As String. ByVal curSalary As Decimal)
m_Name = thename
m_Salary = curSalary
m_EmployeeID = m_EmployeeID + 1
End Sub
Readonly
Property Employeeld() As Integer
Get
Employeeld = m_EmployeeID
End Get
End Property
End Class
Ниже приведена небольшая программа для тестирования класса Empl oyee, а также полный код класса с общим полем:
Option Strict On Module Modulel
Sub Main()
Dim Tom As New Employee("Tom". 100000)
System.Console.WriteLine(Tom.TheName & "is employee! " & _
Tom. Employee ID & "with salary " & Tom.SalaryO)
Dim Sally As New Employee("Sally". 150000)
System.Console.WriteLine(Sally.TheName & "is employee!" & _
Sally.EmployeeID &"with salary "SSally.Salary())
System.Console.WriteLine("Please press the Enter key")
System.Console.Read()
End Sub
End Module
Public Class Employee
Private m_Name As 'String
Private m_Salary As Decimal
Private Shared m_EmployeeID As Integer = 1
Public Sub New(ByVal theName As String.ByVal curSalary As Decimal)
m_Name = thename
m_Salary = curSalary
m_EmployeeID = m_EmployeeID + 1
End Sub Readonly Property Employeeld()As Integer
Get
Employeeld = m_EmployeeID
End Get End Property Readonly
Property TheName() As String
Get
TheName = m_Name
End Get . End Property Readonly
Property Salary () As Decimal
Get
Salary = m_Sa1ary
End Get
End Property
End Class
Обращения к константам в классах не отличаются от обращений к общим полям, но при объявлении констант вместо Shared используется ключевое слово Const. Конечно, объявление открытых констант не приводит к нарушению инкапсуляции.
Не путайте общие данные со статическими. Общие данные существуют в одной копии для всех экземпляров класса, поэтому с точки зрения экземпляров они неявно обладают глобальной видимостью. Статическими называются переменные, состояние которых просто запоминается для повторного использования. Статическими могут быть объявлены как общие, так и обычные поля класса.
Определение классов в программе
От использования готовых классов .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 (доступ разрешается только из текущей программы). Впрочем, пропускать атрибуты уровня доступа при объявлении класса не рекомендуется — особенно если учесть, что уровень доступа, принятый по умолчанию, не является минимальным.
Определение структур в программе
Определение структуры начинается с модификатора уровня доступа и ключевого слова Structure:
Public Structure NameOfStructure
' Код структуры End Structure
Для каждого члена структуры должен быть указан модификатор доступа (например, Public или Private). Поля, объявленные с ключевым словом Dim вне процедур и функций, считаются открытыми. Ниже приведен простейший вариант структуры для работы с комплексными числами:
Public Structure ComplexNumber
Private m_real As Double
Private m_complex As Double
Public Property real () As Double Get
Return m_real
End Get Set(ByVal Value As Double)
m_real = Value
End Set
End Property
Public Property complex()As Double Get
Return m_complex End Get Set(ByVal Value As Double)
m_complex = Value
End Set
End Property
Public Sub New(ByVal x As Double. ByVal у As Double)
real = x complex = у
End Sub
Public Function Add(ByVal zl As ComplexNumber) As ComplexNumber
Dim z As ComplexNumber
z.real = Me.real + zl.real
Z.complex = Me.complex + zl.complex
Return z End Function
' И т.д. End Structure
Обратите внимание на возвращение структуры функцией Add. Кстати, поля структур не могут инициализироваться при объявлении:
Private m_real As Double = 0 ' Ошибка
Между структурами и ссылочными объектами существует еще одно принципиальное различие: использование открытых полей вместо свойств Get-Set в структурах широко распространено и не считается проявлением плохого стиля программирования, как для объектов. Это связано с тем, что поля экземпляров обычно относятся к базовым типам. Например, переопределение приведенной выше структуры ComplexNumber с открытыми полями Real и Imaginary не вызовет особых проблем.
Структуры создаются вызовом New или при присваивании значений их полям. Обращения к полям структур осуществляются так же, как и обращения к свойствам объектов. Ниже приведен пример использования структуры Compl exNumber:
Sub Main()
Dim Z1 As New ComplexNumber(2.3. 2.4)
Dim Z2.Z3 As ComplexNumber
Z2.real = 1.3
Z2.complex =1.4
Z3 = Zl.Add(Z2)
Console. WriteLine(Z3. real)
Console.ReadLine()
End Sub
Текущая версия VB .NET не позволяет переопределять смысл операторов (то есть про-изводить перегрузку операторов), поэтому нам пришлось определить метод Add вместо того, чтобы задать новое определение для оператора «+». Возможность перегрузки операторов должна появиться в будущих версиях VB .NET. Если вы хотите, чтобы в сегодняшней версии вашего пакета для работы с комплексными числами сложение выполнялось знаком «+», придется использовать С#.
Структуры могут содержать любые объекты VB .NET, в том числе другие структуры, перечисляемые типы, массивы и т. д. Таким образом, на VB .NET можно написать пакет для работы с матрицами, в котором основная структура данных будет определяться следующим образом:
Public Structure Matrix
Private TheOata(,) As Double
' И т.д. End Structure
Отладка объектно-ориентированных программ
Отладка объектно-ориентированных программ всегда начинается с анализа объектных переменных и проверки того, соответствует ли их состояние предполагаемому. Именно по этой причине в VS IDE предусмотрены средства для получения информации о закрытых полях ваших классов — окна просмотра (Watch) и локальных переменных (Locals). Применение этих средств отладки будет рассмотрено на простом примере. Допустим, мы решили перейти от связанного списка к двусвязному. Проще говоря, в каждом элементе должна храниться не одна ссылка, а две — на следующий и на предыдущий элемент списка, чтобы перебор мог осуществляться не только в прямом, но и в обратном направлении. Ниже приведен первый вариант класса двусвязного списка, содержащий ошибку. На этом примере будут продемонстрированы основные приемы отладки объектно-ориентированных программ:
1 Option Strict On
2 Module Modulel
3 Sub Main()
4 Dim alinkList As New LinkedList("first link")
5 Dim aLink As LinkedList.Link
6 aLink = aLinklist.MakeLink(aLinkList.GetFirstLink, "second link")
7 aLink = aLinkList.MakeLinktaLink, "third link")
8 Console.WriteLine(aLinkList.GetFirstLink.MyData)
9 aLink = aLinkList.GetNextLink(aLinkList.GetFirstLink)
10 Console.Wri teLine(aLi nk.MyData)
11 Console.WriteLineCaLink.NextLink.MyData)
12 Console. ReadUne()
13 End Sub
14 Public Class LinkedList
15 Private m_CurrentLink As Link
16 Private nfFirstUnk As Link
17 Sub New(ByVal theData As String)
18 m_CurrentLink = New Link(theData)
19 m_FirstLink = m CurrentLink
20 End Sub
21 Public Function MakeLinktByVal currentLink As Link. ByVal _
22 theData As String) As Link
23 m_CurrentLink = New LinkCcurrentLink.theData)
24 Return m_CurrentLink
25 End Function
26 Public Readonly Property GetNextLink(ByVal aLink As Link)_
27 As Link
28 Get
29 Return aLink.NextLink()
30 End Get
31 End Property
32 Public Readonly Property GetCurrentLink() As Link
33 Get
34 Return m_CurrentLink
35 End Get
36 End Property
37 Public Readonly Property GetFirstLink() As Link
38 Get
39 Return m_FirstLink
40 End Get
41 End Property
42
43 ' Вложенный класс для ссылок
44 Friend Class Link
45 Private m_MyData As String
46 Private m_NextLink As Link
47' Private m_ParentLink As Link
48 Friend Sub New(ByVal myParent As Link. ByVal theData As String)
49 m_MyData = theData
50 m_Parentlink = Me
51 m_NextLink = myParent
52 End Sub
53 Friend Sub New(ByVal theData As String)
54 m_MyData = theData
55 End Sub
56 Friend Readonly Property MyData() As String
57 Get
58 Return m_MyData
59 End Get
60 End Property
61 Friend Readonly Property NextLink() As Link
62 Get
63 Return m_NextLink
64 End Get
65 End Property
66 End Class
67 End Class
68 End Module
Результат работы программы показан на Рисунок 4.11. Конечно, это совсем не то, что мы ожидали получить.
Отношения между классами в программах
В традиционном ООП предусмотрены три типа отношений между классами:
Использование: непосредственная зависимость.Включение: иногда называется агрегированием. Реализует логические связи типа «является составной частью».
Наследование: реализует логические связи типа «является частным случаем».
В таких языках, как VB .NET, C# и Java, кроме классических типов существует четвертый тип отношений между классами — реализация интерфейса (отношение типа «поддерживает»). Суть реализации интерфейса заключается в том, что для поддержки некоторых функциональных возможностей ваш класс принимает на себя обязательства, по которым он должен содержать определенные члены. Интерфейсы существуют в VB начиная с версии 5 и часто используются в VB .NET. В главе 5 эта тема рассматривается гораздо подробнее.
Вернемся к классической тройке. Отношение использования, самое очевидное и распространенное, всего лишь означает, что один класс зависит от другого. Во всех ситуациях, когда один объект посылает сообщение другому объекту, можно говорить о зависимости между этими объектами. В обобщенном случае класс А использует класс Б, если:
или
Постарайтесь свести к минимуму количество взаимодействующих классов. Иначе говоря, избегайте лишних связей между классами, без которых можно обойтись. Если класс А не использует класс Б, то изменения в классе Б никак не отразятся на работе класса А (а следовательно, модификация класса Б не станет причиной ошибок в классе А!).
Термин «включение» (агрегирование) означает, что объект класса А содержит внутренние объекты класса Б.
На базе включения реализуется методика делегирования, когда поставленная перед внешним объектом задача перепоручается внутреннему объекту, специализирующемуся на решении задач такого рода. Агрегирование с делегированием методов было очень распространенным явлением в прежних версиях VB, поскольку этот принцип использовался при создании новых элементов (вспомните, как создавались новые, специализированные текстовые поля — вы размещали текстовое иоле внутри формы пользовательского элемента, а затем запускали программу-мастер, которая автоматически генерировала код делегирования).
Агрегирование по-прежнему широко используется в VB .NET, но во многих ситуациях ему на смену приходит наследование — третий тип отношений между классами. Наследование считается одним из четырех «краеугольных камней» ООП наряду с абстракцией, инкапсуляцией и полиморфизмом. Все четыре концепции будут рассмотрены в ближайших четырех разделах.
Параметризованные конструкторы
На первый взгляд конструктор New работает так же, как в предыдущих версиях VB. В действительности изменилось очень многое, и самое принципиальное изменение заключается в том, что при вызове New теперь могут передаваться параметры. Как вы вскоре увидите, в пользовательских классах переопределенная версия New замещает событие Initial ize из прежних версий VB, которое не поддерживало параметров.
Например, для класса Random определены две версии конструктора. Первая версия вызывается без параметров, как показано выше. В этом случае вы получаете случайные числа, полученные в результате случайной инициализации генератора по показаниям системных часов. Другая версия выглядит так:
Dim aRandomlnstance As Random
aRandomlnstance = New Random(42)
Эта версия класса Random генерирует одну и ту же последовательность случайных чисел, начинающуюся с числа 42 (эта возможность абсолютно необходима в процессе отладки).
Как ни странно, появление параметризованных конструкторов в VB сделало для полноценной реализации ООП едва ли не больше, чем поддержка наследования. Если наследование еще можно заменить в программе другими средствами (обычно агрегированием), то компенсировать отсутствие параметризованных конструкторов гораздо труднее. Параметризованные конструкторы нужны прежде всего для того, чтобы предотвратить случайное создание объекта в неопределенном состоянии. В прежних версиях VB это всегда порождало массу проблем, поскольку событие Initialize вызывалось без параметров. Оставалось лишь следовать общепринятой схеме — включать в класс функцию инициализации объектов (обычно этой функции присваивалось имя Create) и надеяться на то, что пользовать класса не забудет вызвать эту функцию. В противном случае объект не инициализировался, а поля экземпляра сохраняли значения по умолчанию, что приводило к появлению тонких, неуловимых ошибок.
В VB .NET, как во всех объектно-ориентированных языках, объект создается только конструктором. Более того, ниже будет показано, как потребовать обязательной передачи параметров при вызове конструктора — это гарантирует, что объект не будет создан в неопределенном состоянии.
Определение нескольких версий одной функции, различающихся только типом пара-метров, называется перегрузкой (overloading). Как будет показано ниже, в VB .NET перегрузка поддерживается не только для конструкторов New, но для любых функций и процедур. Перегрузка также используется для решения проблемы с передачей необязательных параметров.
Перечисляемые типы
Перечисляемые типы обычно используются для определения набора именованных целочисленных констант. При определении перечисляемого типа используется пара ключевых слов Enum-End Enum вместе с модификатором доступа. Перечисляемый тип может содержать только целочисленные типы вроде Integer или Long (тип Char недопустим). Например, в следующем фрагменте определяется открытый перечисляемый тип с именем BonusStructure:
Public Enum BonusStructure
None = 0
FirstLevel = 1
SecondLevel = 2
End Enum
После этого в любом месте программы можно объявить переменную типа BonusStructure: Dim bonusLevel As BonusStructure
При работе с перечисляемыми типами, как и с другими структурными типами, ключевое слово New не используется.
Если в перечисляемом типе указаны только имена без числовых значений, .NET начи-нает отсчет с 0 и увеличивает значение на 1 для каждой новой константы. Если задано только первое число, то каждое следующее значение вычисляется увеличением предыдущего на 1.
Определив в проекте перечисляемый тип, вы можете использовать конструкции вида
Bonus =Tom.Sales * bonusLevel.SecondLevel
Поскольку перечисляемые типы неявно интерпретируются как общие, в ссылках на них можно указывать имя перечисляемого типа вместо имени переменной:
Public Function Calcu1ateBonus(ByVal theSales As Decimal) As Decimal
Return theSales * BonusStructure.SecondLevel
End Function
Одним из традиционных недостатков перечисляемых типов было отсутствие удобных средств для получения имени по значению, что затрудняло отладку программ. В классе Enum, базовом для всех перечисляемых типов, определены очень полезные методы для получения подобной информации. Например, следующая команда возвращает строку FirstLevel :
BonusStructure.GetName(bonusLevel .GetType.l)
Данный фрагмент выводит все имена, входящие в перечисляемый тип:
Dim enumNames As String().s As String
enumNames = BonusStructure.GetNames(bonusLevel.GetType)
For Eachs In enumNames
System.Console.WriteLine(s) Next
Перегрузка членов класса
Метод 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
Переход к использованию объектов
С давних времен в программировании использовалась структурная, процедурно-ориентированная модель. Сначала программист разбирался, что должна делать программа, а затем выбирал одно из двух:
-
Задача разбивалась на подзадачи; те, в свою очередь, делились на подзадачи следующего уровня и т. д. Это продолжалось до тех пор, пока упрощение подзадач не позволяло реализовать их непосредственно (подход «сверху вниз»).
Программист писал процедуры для решения простых задач и последовательно объединял их в более сложные процедуры, пока недобивался нужного эффекта (подход «снизу вверх»).
Конечно, многие опытные программисты не следовали рекомендациям теоретиков, выступавших за первый способ, и предпочитали решать практические задачи комбинацией этих двух стратегий [ В программировании это обычно называется встречным движением. ].
Между ООП и процедурно-ориентированным программированием существуют два важных различия:
-
В ООП программист сначала выделяет классы, образующие объектную модель, и только после этого переходит к анализу их методов и свойств.
Методы и свойства ассоциируются с классом, предназначенным для выполнения соответствующих операций.
Возникает очевидный вопрос: по каким критериям выделять классы в программе? Для этого имеется хорошее эмпирическое правило, которое связывает компоненты объектной модели с частями речи. Классы соответствуют существительным в постановке задачи. В нашем примере центральное место занимает существительное «работник» (Employee). Методы объектов соответствуют глаголам — например, работнику можно повысить зарплату (метод RaiseSalary). Свойства соответствуют прилагательным, описывающим существительные. Разумеется, это соответствие лишь намечает контуры объектной модели. Только практический опыт поможет вам решить, какие существительные, глаголы и прилагательные важны, а какие являются второстепенными.
Сейчас стоит повторить золотое правило программирования, нисколько не изменившееся с переходом на ООП: будьте проще. Использование простых классов заметно упрощает объектно-ориентированное программирование. Класс с простой внутренней структурой и небольшим числом внешних связей проще понять, а следовательно, и запрограммировать.
Описание логических связей между классами играет в ООП настолько важную роль, что появилась целая наука о построении диаграмм, иллюстрирующих отношения между классами. Чаще всего для описания логических связей применяется язык UML (Uniform Model Language). Средства построения диаграмм входят во многие системы автоматизированной разработки программ — такие, как Microsoft Visual Modeler и Visio, а также Rational Rose компании Rational Software (Visual Modeler входит в некоторые версии VS .NET).
Некоторые пакеты на основании диаграммы автоматически генерируют базовый код классов. За общими сведениями о UML мы рекомендуем обращаться на web-сайт Rational (www.rational.com/uml).
Полиморфизм
В традиционной трактовке термин «полиморфизм» (от греческого «много форм») означает, что объекты производных классов выбирают используемую версию метода в зависимости от своего положения в иерархии наследования. Например, и в базовом классе Employee, и в производном классе Manager присутствует метод для повышения зарплаты работника. Тем не менее метод RaiseSalаrу для объектов класса Manager работает не так, как одноименный метод базового объекта Employee.
Классическое проявление полиморфизма при работе с классом Manager, производным от Empl oyee, заключается в том, что при вызове метода по ссылке на Empl oyee будет автоматически выбрана нужная версия метода (базового или производного класса). Допустим, в программе метод RaiseSalary вызывается по ссылке на Employee.
В противном случае вызывается стандартный метод RaiseSalary базового класса.
В VB5 и VB6 смысл термина «полиморфизм» был расширен, и к традиционному полимор-физму на базе наследования добавился полиморфизм на базе интерфейсов (объект, реализующий интерфейс, вызывал метод интерфейса вместо другого метода с тем же именем). Объект, реализующий интерфейс Manager, правильно выберет метод RaiseSalary в зависимости от контекста использования.
В обоих случаях объект выбирает метод в зависимости от полученного сообщения. При отправке сообщения не нужно знать, к какому классу фактически принадлежит объект; достаточно разослать сообщение всем объектам Employee и поручить выбор полиморфного метода компилятору.
Следующий пример показывает, почему полиморфизму придается такое большое значение. Одному из авторов доводилось консультировать компанию, занимавшуюся компьютерной обработкой медицинских анализов. Каждый раз, когда в процесс тестирования включался новый реактив, программистам приходилось просматривать многие тысячи строк кода, искать команды Select Case и добавлять в них секции Case для нового реактива. Стоило пропустить хотя бы одну... и нам бы не хотелось, чтобы этот реактив испытывался на наших анализах крови. Конечно, исправление многих команд Select Case превращало сопровождение в настоящий кошмар, требующий долгих часов тестирования.
Полиморфизм позволяет написать программу, которая в подобной ситуации ограничивается единственным изменением. Все, что от вас потребуется, — определить для нового реактива новый класс и правильно запрограммировать в нем переопределяемые или добавленные методы.
Почему? Потому что в главной программе можно будет использовать конструкции следующего вида:
For Each reagent in Reagents
reagent.Method
Next
Показанный цикл будет автоматически работать с новым реактивом, а необходимость в долгих поисках Sel ect Case отпадет.
Select Case reagent Case iodine
' Действия с йодом Case benzene
' Действия с бензолом
' И т. д. для 100 разных случаев в 100 местах
В приведенном выше фрагменте цикл For Each перебирает все возможные реактивы, и благодаря волшебному свойству полиморфизма компилятор найдет метод, который должен вызываться для каждого конкретного реактива. Правильное использование полиморфизма избавит вас от громоздких команд Select Case, выбирающих нужное действие в зависимости от типа объекта.
Практическое использование вложенных классов на примере связанного списка
Вложенные классы чаще всего применяются в реализациях различных структур данных. К числу самых распространенных структур данных принадлежит связанный список. Он представляет собой цепочку ссылок, которая позволяет легко переходить от текущего объекта к следующему, однако поиск всегда начинается с конкретной ссылки. Применение вложенных классов в реализации связанного списка выглядит вполне естественно, поскольку код объектов-ссылок не представляет интереса для пользователей класса LinkedList, а объекты Link не могут существовать независимо от содержащего их объекта LinkedList.
Ниже приведена очень простая реализация класса для работы со связанными списками. Просмотрите ее, а затем мы подробно проанализируем листинг. Обратите внимание на важную строку, выделенную жирным шрифтом (строка 49); в ней используется нетривиальная особенность объектно-ориентированного программирования, о которой будет рассказано ниже.
1 Option Strict On
2 Module Modulel 3 Sub Main()
4 Dim aLinkedList As New LinkedlistC'first link")
5 Dim aALink As LinkedList.Link
6 aLink = aLinkedList.MakeLink(aLinkedList.GetFirstLink,"second link")
7 aLink = aLinkedList.MakeLink(aLink,"third link")
8 Console.WriteLine(aLinkedList.GetFirstLink.MyData)
9 aLink = aLinkedList.GetNextLink(aLinkedList.GetFirstLink)
10 Console.WriteLine(aLink.MyData)
11 Console.WriteLine(aLink.NextLink.MyData)
12 Console. ReadLine()
13 End Sub
14 Public Class LinkedList
15 Private m_CurrentLink As Link
16 Private m_FirstLink As Link
17 Sub New(ByVal theData As String)
18 m_CurrentLink = New Link(theData)
19 m_FirstLink = in_CurrentLink
20 End Sub
21 Public Function MakeLink(ByVal currentLink As Link.ByVal
22 theData As String) As Link
23 m_CurrentLink =New Link(currentLink.theData)
24 Return m_CurrentLink
25 End Function
26 Public Readonly Property GetNextLink(ByVal aLink As Link)_
27 As Link
28 Get
29 Return aLink.NextLink()
30 End Get
31 End Property
32 Public Readonly Property GetCurrentLink()As Link
33 Get
34 Return m_CurrentLink
35 End Get
36 End Property
37 Public Readonly Property GetFirstUnkOAs Link
38 Get
39 Return m_FirstLink
40 End Get
41 End Property
42
43 ' Вложенный класс для ссылок
44 Friend Class Link
45 Private m_MyData As String
46 Private m_NextLink As Link
47 Friend Sub New(ByVal myParent As Link.ByVal theData As String)
48 m_MyData - theData
49 myParent.m_NextLink = Me
50 ' End Sub
51 Friend Sub New(ByVal theData As String)
52 m_MyData =theData
53 End Sub
54 Friend Readonly Property MyData()As String
55 Get
56 Return m_MyData
57 End Get
58 End Property
59 Friend Readonly Property NextLink()As Link
60 Get
61 Return m_NextLink
62 End Get
63 End Property
64 End Class
65 End Class
66 End Module
Строка 4 создает новый экземпляр связанного списка. В строке 5 определяется объектная переменная типа Link. Поскольку класс Link является вложенным по отношению к LinkedList, его тип записывается в виде «полного имени» LinkedList.Link. Строки 6-12 содержат небольшую тестовую программу.
В строках 17-20 определяется конструктор класса LinkedList, в котором вызывается второй конструктор класса Link (строки 51-53). Последний объявлен с атрибутом Friend и потому доступен для внешнего класса Li nkedLi st. Если бы конструктор Link был объявлен с атрибутом Private, то он стал б"ы недоступным для внешнего класса.
Также стоит обратить внимание на то, как в первом конструкторе класса Link (строки 47-50) организуется ссылка на только что созданный элемент списка из предыдущего элемента. Для этого используется ключевое слово Me — это очень принципиальный момент, поэтому строка 49 выделена в листинге жирным шрифтом. На первый взгляд команда myParent.m_NextLink = Me выглядит недопустимой, поскольку мы обращаемся к закрытому полю родительского класса myParent. Однако программа все-таки работает! Итак, запомните очень важное правило:
Для экземпляра класса всегда доступны закрытые поля других экземпляров этого класса.
При написании подобных классов в VB .NET можно обойтись и без использования этой нетривиальной особенности классов. Например, в класс Link можно включить специальный метод для создания ссылки на следующий элемент списка. В конечном счете выбор зависит только от вашего стиля программирования. Тем не менее сама возможность обращения к закрытым членам класса может преподнести неприятные сюрпризы, и об этом необходимо знать. По этой причине в нашем примере продемонстрирован именно такой подход.
Преимущества ООП
На первый взгляд классы ООП и связанные с ними методы и свойства имеют много общего с процедурным подходом и модульным строением программ. Ключевое различие заключается в следующем:
Класс представляет собой шаблон для создания объектов, состояние которых изменяется со временем.
Выглядит слишком абстрактно? И вроде бы не имеет никакого отношения к программированию VB? Вспомните панель элементов Visual Basic. В прежних версиях VB каждая кнопка панели создавала объект, являющийся экземпляром класса соответствующего элемента.
А если бы панель элементов, готовая в любой момент создать новое текстовое поле или кнопку по вашему запросу, куда-то исчезла? Только представьте, какими сложными станут программы VB, если каждое текстовое поле придется оформлять в виде отдельного модуля! Кстати говоря, один модуль нельзя подключить к программе дважды, поэтому создание формы с двумя одинаковыми текстовыми полями потребует довольно изощренного программирования.
Благодаря существованию панели элементов VB всегда был объектно-ориентированным языком. Начиная с версии 4 в нем появилась возможность создавать некоторые типы объектов. Но только в VB .NET программист может определять классы для любых объектов и в полной мере использовать средства ООП на том же уровне, что и в C++ и С#. Более того, все языки .NET обеспечивают примерно равную эффективность п$и работе с классами.
класс String
Другим хорошим примером класса с несколькими конструкторами является класс String. Хотя для конструирования строк предусмотрена сокращенная запись (последовательность символов, заключенная в кавычки), в более сложных случаях лучше перейти на использование конструкторов. Например, один из конструкторов создает строку, состоящую из нескольких копий одного символа. Следующая команда создает строку из 37 пробелов:
Dim str As String = New String((CChar(" "), 37)
В данном случае вместо конструктора можно воспользоваться функцией Space (учтите, что в режиме жесткой проверки типов Option Strict On строка, состоящая из одного символа «пробел», должна преобразовываться в символ специальной функцией).
Обобщенная форма этого конструктора выглядит так:
New (ByVal с as Char, ByVal count As Integer)
При работе с классом Stri ng также часто используется конструктор New(ByVal val ue() As Char, который получает массив символов и преобразует их в строку.
Поскольку строковые переменные в VB .NET стали объектными, при вводе «.» после имени строковой переменной появляется подсказка IntelliSense со списком членов класса String.
класс StringBuilder
Чрезвычайно полезный класс StringBuilder входит в пространство имен System.Text (пространства имен рассматриваются в следующем разделе). Этот класс следует использовать вместо обычного класса String в тех случаях, когда вы собираетесь внести постоянные изменения в строку. Дело в том, что при каждом изменении строки (даже при простом добавлении нового символа) VB .NET приходится создавать новую строку, а эта операция требует времени. При работе с экземпляром класса StringBuilder VB .NET обходится модификацией исходного объекта.
При создании пустого объекта Stri ngBui I der методом New VB .NET резервирует блок памяти для 16 символов и автоматически наращивает его при включении новых символов. Объект StringBuilder можно рассматривать как «интеллектуальный» массив символов, который увеличивается и уменьшается по мере надобности и поэтому в каком-то смысле напоминает тип Stri ng в V-B6. Текущий размер объекта Stri ngBui I der называется вместимостью (capacity). В классе Stri ngBui I der определены шесть конструкторов, перечисленных в табл. 4.1.
Проблемы с передачей объектных переменных по значению
Большинство языков программирования требует четкого понимания, чем передача параметров по ссылке отличается от передачи по значению. Не забывайте, что в VB .NET параметры по умолчанию передаются по значению (ByVal).
Большинство программистов руководствуется простым правилом: если параметр передавался по ссылке, его изменения сохраняются в исходной переменной, а если по значению — изменения теряются после выхода из функции или процедуры. К сожалению, в случае с объектными переменными это правило не всегда истинно. Попробуйте выполнить следующий фрагмент, в котором массив передается в процедуру по значению. Вы убедитесь в том, что исходный массив изменяется после вызова процедуры!
Module Modulel Sub Main()
Dim a() As String ={"HELLO"."GOODBYE"}
Console.WriteLineC'Original first item in array is:" & a(0))
Console.WriteLineC'Original second item in array is:" & a(1))
Yikes(a) ' Массив передается по значению!
Console.WriteLineC'After passing by value first item in array now is:"_
&A(0))
Console.WriteLine("After passing by value second item in array is:"_
&АШ)
Console. ReadLine()
End Sub
Sub Yikes(ByVal Foo As String())
Foo(0) = "GOODBYE"
Food) = "HELLO"
End Sub
End Module
Пространства имен
При использовании кода, написанного другими программистами, всегда возникает потенциальная опасность конфликтов имен. Но даже если забыть об этих конфликтах, все равно необходимо каким-то образом сгруппировать тысячи методов в соответствии с их функциональным назначением. Следовательно, в огромной библиотеке классов .NET должен существовать какой-либо способ логической группировки информации. В .NET эта задача решается при помощи пространств имен. Конечно, вы можете создавать собственные пространства имен; ниже в этой главе мы покажем, как это делается.
В каждом городе есть своя Главная Улица, а в каждой библиотеке непременно найдется метод с именем Open. Пространства имен позволяют различать эти методы. Например, в пространстве имен System. 10 собраны методы для выполнения файловых операций; в него входит класс Fil е, а в этом классе имеется метод Open. Полное имя метода выглядит так:
System.I0.File.Open
Класс File является частью пространства имен System. I0, поэтому он не конфликтует с другим классом File из пространства имен Cornell .Morrison.NiftyClasses, который также может содержать собственный метод Open.
Пространства имен для создаваемых классов
Классы, перечисляемые типы, структуры или модули включаются в пространства имен. Конечно, создать экземпляр модуля невозможно — только экземпляры классов, определяемых в модуле. В диалоговом окне Project Properties, показанном на Рисунок 4.9, присутствуют текстовые поля для имени сборки и корневого пространства имен,
На Рисунок 4.9 указано корневое пространство имен Apress. При объявлении пространства имен в программе можно использовать иерархию произвольной глубины, отражающую логическую структуру нашей программы. Рассмотрим пример определения класса:
Namespace Cornell.Morrison.VB.NET.CH4
Module Module1
Sub Main()
Console.filriteLine("test code goes here")
End Sub
Public Class"EmployeeExamplel
' Код класса End Class
End Module
End Namespace
Пространство имен и имя сборки
Теперь, когда вы знаете, как определять собственные классы, вам будет проще работать с окном классов, в котором члены классов вашего решения отображаются в виде удобного иерархического дерева. Окно классов помогает ориентироваться в коде вашего решения: при двойном щелчке в одной из строк окна классов в окне программы автоматически открывается код соответствующего члена. Окно классов открывается командой View > Class View или комбинацией клавиш Ctrl+Shift+C. На Рисунок 4.10 показано, как выглядит окно классов для одной из версий нашего класса Employee.
Рисунок 4.10. Окно классов для класса Employee
В левом верхнем углу окна расположена пара кнопок. Кнопка New Folder создает новую папку, но чаще используется кнопка Class View Sort By Type. Она открывает список, в котором выбирается режим представления информации в окне.
Sort By Type. Классы и члены упорядочиваются по типу. Например, в этом режиме удобно сгруппировать все свойства (базовых классов, интерфейсов, методов и т. д.).
Sort By Access. Классы и члены упорядочиваются по уровню доступа.
Group By Type. Классы и члены группируются в разных узлах в зависимости от типа. Например, все свойства объединяются в узле Properties, а все поля — в узле Fields.
Результат работы программы с
Глава получилась очень длинной. В ней вы познакомились с некоторыми встроенными классами .NET Framework, но главной темой была специфика работы с объектами в VB .NET. По сравнению с прежними версиями VB в этой области произошло много изменений, в основном принципиальных. В частности, были рассмотрены параметризованные конструкторы, значительно повышающие надежность создания объектов и их инициализацию. Короче говоря, в этой главе был изложен базовый материал, абсолютно необходимый для дальнейшего освоения VB .NET.
Снова о конструкторах
Если в вашем классе не определен конструктор, 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; оно обозначает конкретный объект, код которого выполняется в настоящий момент.
Снова о свойствах
Принципиальное различие в работе свойств VB6 и VB .NET заключается в том, что секции Get и Set теперь должны обладать одинаковым уровнем доступа. Определять свойства с секциями Public Get и Private Set в VB .NET не разрешается.
Это ограничение легко обходится. Чтобы процедура Set фактически стала закрытой, объявите свойство с атрибутами Public Readonly и одновременно объявите другое, внутреннее закрытое свойство для Set.
Кроме того, в VB6 свойство не могло изменяться в процедуре, даже если оно было передано по ссылке (то есть с ключевым словом ByRef). В VB .NET свойства, переданные по ссылке, могут изменяться.
Но самое принципиальное изменение относится к свойствам по умолчанию. В прежних версиях VB существовала концепция свойств по умолчанию, которая на первый взгляд казалась очень удобной. На практике свойства по умолчанию часто становились причиной ошибок в программах. Например, что означает следующая команда?
Me.Text1 = Text2
Отслеживая ошибки, возникающие в подобных командах, опытные пользователи VB выясняли, что эта команда задает свойству Text текстового поля с именем Textl значение переменной Text2. Свойства по умолчанию не только становились источником ошибок в программах, но и требовали, чтобы при присваивании объектов использовалось ключевое слово Set, поскольку присваивание объектам нужно было отличать от присваивания свойствам. В VB .NET проблема свойств по умолчанию решается просто — они разрешены только там, где это действительно оправдано, а именно при использовании параметров. Допустим, у вас имеется кэш-таблица aTable; при выборке значений было бы удобно использовать синтаксис вида aTable("theKey"), но это возможно лишь в том случае, если Item является свойством по умолчанию для класса HashTable. Свойства по умолчанию объявляются в классе с ключевым словом Default, причем это допускается лишь для свойств, получающих минимум один параметр. Если свойство по умолчанию перегружается, все перегруженные версии также помечаются ключевым словом Default. Свойства по умолчанию чаще всего используются в ситуации, когда у объекта имеется свойство, значение которого возвращается в виде массива или другого объекта, способного вмещать несколько величин (например, хэш-таблицы). Предположим, у вас имеется класс Sal es и свойство InYear, которое по полученному индексу возвращает число (объем продаж):
Public Class Sales
Private m_Sales() As
Decimal = {100, 200. 300}
Default Public Property InYear(ByVal theYear As Integer) As Decimal
Get
Return m_Sales(theYear)
End Get
Set(ByVa1 Value As Decimal)
m_Sales(theYear)=Value
End Set
End Property
' Остальной код класса End Class
Свойство по умолчанию позволяет использовать конструкции вида
Dim ourSales As New Sales()
Console.WriteLine(ourSa1es(1))
вместо
Dim ourSales As New Sales()
Console.WriteLi ne(ourSales.InYear(1))
Или, например, вы можете написать
ourSales (2) = 3000
вместо
ourSales.InYear(2) = 3000
Ключевое слово Set используется в процедурах свойств VB NET.
Создание объектов в VB .NET
В VB .NET, как и в прежних версиях VB, объекты создаются ключевым словом New (исключение составляют строки и массивы — для создания этих объектов предусмотрена сокращенная запись).
Рассмотрим практический пример — в .NET Framework входит полезный класс Random для работы со случайными числами. По своим возможностям этот класс превосходит функцию Rnd, сохраненную в языке для обеспечения обратной совместимости. Например, класс Random позволяет заполнить байтовый массив случайными числами от 0 до 255 или сгенерировать положительное случайное число в заданном интервале. Однако Random — не функция, а класс, методы которого вызываются с указанием конкретного экземпляра. А для этого необходимо предварительно создать экземпляр (проще говоря, объект) класса Random.
Сделать это можно несколькими способами, и в любом варианте используется ключевое слово New. Самый наглядный, хотя и не самый компактный способ заключается в отделении объявления класса от вызова New:
Dim aRandomlnstance As Random
' Объявление aRandomlnstance = New Random()
' Создание экземпляра
Многие программисты предпочитают использовать сокращенную запись:
Dim aRandomlnstance As New Random
' Экземпляр создается при объявлении
Эта команда эквивалентна приведенному выше фрагменту; в ней используется такая возможность VB .NET, как инициализация переменных при объявлении.
На языке ООП метод New называется конструктором, поскольку он предназначен для создания (конструирования) экземпляров класса.
Программисты, работавшие с предыдущими версиям VB, должны обратить внимание на следующее: в VB .NET не поддерживается ключевое слово Set (некоторые побочные эффекты его исчезновения описаны в разделе «Свойства» настоящей главы). Два варианта синтаксиса New различаются только реакцией на исключения, возникающие при создании объектов (см. главу 7).
В прежних версиях VB между полной и сокращенной формой вызова конструктора существовали тонкие различия, связанные с тем, что при сокращенной записи создание объекта откладывалось до момента первого использования. В VB .NET эта особенность была исключена.
Некоторые программисты (особенно работающие на С# и Java) предпочитают третий вариант синтаксиса, который выглядит как комбинация первых двух:
Dim foo As Random = New Random()
' В стиле C#/Java
Он ничем не отличается от второго варианта синтаксиса.
Метод New позволяет конструировать объекты в любом выражении VB .NET, если результат соответствует контексту. Следующая команда VB .NET вполне допустима (хотя понять ее непросто, поэтому использовать подобный стиль программирования не рекомендуется):
Consolе.WriteLi net New Random().Next())
Впрочем, подобные конструкции могут встретиться в чужих программах, которые вам придется сопровождать. Особенно часто они используются программистами с опытом работы на C++/Java.
Создав экземпляр класса Random, вы можете пользоваться его методами и свойствами при помощи знакомого «точечного» синтаксиса. Библиотека .NET Framework содержит множество классов; технология IntelliSense всегда напомнит вам, что можно сделать с тем или иным экземпляром класса (Рисунок 4.1).
Справочная система и .NET Framework
В библиотеку .NET Framework входят сотни пространств имен, каждое из которых содержит множество полезных классов. По масштабам и возможностям .NET Framework сравнима с полным интерфейсом Win32 API. Библиотека настолько огромна, что описать ее в одной книге попросту невозможно. Хотя эта глава дает начальное представление о некоторых классах .NET Framework, как можно скорее приступайте к чтению документации .NET. Начните с раздела «.NET Framework Class Library» и найдите описания пространств имен, представляющих для вас интерес. Как показано на Рисунок 4.4, в справочной системе перечислены все классы каждого пространства имен.
Каждое имя класса в левом столбце представляет собой гиперссылку, ведущую к подробному описанию класса. В нижней части описания класса перечислены имена его членов. Если щелкнуть на любом из этих имен, вы перейдете к подробному описанию соответствующего члена. Обратите внимание: VB .NET уже не считается второстепенным языком — синтаксис всех членов приводится для VB, VC и С#. На Рисунок 4.5 показан пример документации класса Directorylnfo в бета-версии 2.
Чтобы получить подробное описание метода GetDi rectories, использованного в предыдущем примере, щелкните на ссылке Directorylnfo в нижней части страницы, а затем щелкните на ссылке GetDi rectories. Внешний вид страницы показан на Рисунок 4.6. Некоторые термины, встречающиеся на этой странице (такие, как Pri vate), рассматриваются далее в этой главе,
Страница Imports окна свойств
Чтобы рассмотреть пример использования Imports на сколько-нибудь нетривиальном примере, мы возьмем класс Directorylnfo из пространства имен System. IO. Как подсказывает само название, класс Directorylnfo содержит методы для получения информации о содержимом каталогов, вывода полного имени каталога и т. д. Один из конструкторов этого класса получает строку с именем каталога, который вы хотите проанализировать (если переданное имя не является абсолютным, конструктор считает, что оно задается относительно текущего каталога программы). Правильно написанная команда Imports позволяет заменить длинную команду Dim dirlnfo As New System.IO.Directory!nfo("C:\") более компактной и понятной командой
Dim dirlnfo As New DirectoryInfo("C:\")
Следующая программа выводит список всех каталогов на жестком диске, в ее работе используются рекурсия и класс Directorylnfo. Ключевую роль в ней играет метод GetDi rectories (), возвращающий коллекцию подкаталогов. Функция ListDi rectories перебирает содержимое коллекции и рекурсивно вызывается для каждого элемента:
Option Strict On Imports System.IO Module Modulel
Sub Main())
Dim dirlnfo As New DirectoryInfo("C:\")
ListDirectories(dirInfo)
End Sub
Sub ListDirectories(ByVal theDirectory
As Directorylnfo)
Dim tempDir*As DirectoryInfo
Console. Wri.teLi net theDi rectory .Full Name())
For Each terrain In theDi rectory. GetDi rectories ()
ListDirectories(tempOir) Next End Sub End Module
Если вы привыкли к рекурсивному перебору каталогов с использованием старой функции Dir, вы оцените, насколько упростил эту программу в .NET замечательный метод Directorylnfo.
Во время работы над этим примером мы легкомысленно назвали свое решение D1-rectorylnfo. В результате команда Imports перестала работать! Причины так и остались неизвестными, но мораль ясна: не присваивайте своим решениям имена, совпадающие с именами классов библиотеки .NET.
Структуры
Некоторые полагают, что структуры VB .NET аналогичны пользовательским типам прежних версий VB или многих других языков программирования. Конечно, структуры VB .NET могут использоваться как пользовательские типы, но этим область их возможного применения не исчерпана. Структура может обладать всеми признаками традиционного класса, включая конструкторы и члены с атрибутами Private/Friend/Public. Единственное отличие структур от обычных объектов заключается в том, что структуры обладают структурной семантикой. Вспомните, какой смысл вкладывается в этот термин:
структуры создаются без использования оператора New, поэтому для них всегда определено значение по умолчанию, образованное значениями по умолчанию всех полей экземпляра;
в структуре определен метод Equals, который возвращает True, если две структуры содержат одинаковые внутренние данные (метод Equals используется в форме А.Еquals(В)).
В текущей версии VB .NET равенство двух экземпляров структурного типа не может быть проверено при помощи знака равенства (=). Вместо этого следует использовать метод Equals. По умолчанию метод Equals выполняет так называемое поверхностное (shallow) сравнение — смысл этого термина рассматривается в разделе «Клонирование объектов» главы 5. Если вы хотите, чтобы ваша версия Equals отличалась каким-то особым поведением, метод можно переопределить в определении структуры.
Некоторые программисты используют структуры чаще, чем следует, полагая, что структура как1 облегченный объект работает эффективнее, чем объекты обычных классов. К сожалению, этот подход не лишен недостатков: два объекта, обладающие одинаковым состоянием, далеко не всегда должны считаться равными, тогда как при использовании структур это неизбежно. Кроме того, пользователи вашего кода обычно ожидают, что структуры (и структурные типы вообще) по своему поведению близки к встроенным структурным типам вроде Integer и Double.
Все стандартные числовые типы (Integer, Long, и т.д.) реализованы B.NETFramewdrke виде структур.
Свойства и инкапсуляция
На первый взгляд кажется, что свойства очень похожи на открытые поля экземпляров. Если объявить в классе А открытое поле с именем evil, на него можно сослаться при помощи конструкции A.evil; ничто не указывает на то, что свойство реализовано в виде открытой переменной. Может, определить открытое поле и избавиться от хлопот по определению процедур Get и Set?
Не поддавайтесь соблазну. Инкапсуляцию данных не стоит нарушать без веских причин (а еще лучше —те нарушать никогда!).
Но инкапсуляцию можно случайно нарушить и другими способами — например, если не следить за возвращаемыми значениями свойств. Каким образом? Если поле представляет собой изменяемый объект (например, массив), возвращение его в виде значения свойства приведет к нарушению инкапсуляции, поскольку внешний код сможет изменить состояние поля экземпляра через полученную объектную переменную. В таких ситуациях следует создать клон поля (клонирование объектов рассматривается в главе 5). Мораль:
Свойства не должны возвращать изменяемые объекты, которые представляют собой переменные классов.
Конструкторы класса
Сколько времени теряется при создании новых экземпляров строки? Мы провели тестирование (при этом использовалась программа, приведенная в подразделе «Хронометраж — насколько быстрее работает класс StringBuilder?» ниже). Оказалось, что класс StringBuilder обычно работает в сотни раз быстрее, чем класс String. На практике это соотношение может быть и выше, поскольку в наших тестах не использовались дополнительные возможности класса StringBuilder, позволяющие избежать частого выделения памяти. В результате оптимизации класс StringBuilder может работать еще быстрее. С другой стороны, если вы просто обращаетесь к отдельным символам строки и не собираетесь изменять ее, обычный класс String оказывается эффективнее класса StringBuitder.
В следующем фрагменте показано, как быстро создать строку, состоящую из 25 000 повторений буквы А:
Dim bar As New String("A" .25000)
Dim foo As New System.Text.SthngBuilder(Bar)
Свойство Chars позволяет прочитать или записать символ, находящийся в заданной позиции Stri ngBui I der. Индексация начинается с 0, поэтому для экземпляра StringBuilder с именем foo команда foo.Chars(l) = "b" заменяет второй символ строки символом «b».
При помощи свойства Length можно получить или задать текущий размер объекта Stri ngBuilder. Если указанное значение меньше текущего размера, VB усекает объект Stri ngBuilder. Если при создании объекта StringBuilder была задана максимальная вместимость, в случае ее превышения инициируется исключение (исключения рассматриваются в главе 7).
Часто используемые члены класса StrlngBuilder очень сильно перегружены. Они существуют в большом количестве версий, что позволяет выполнять со строками разнообразные операции — включать и удалять в них строки, символы, массивы символов и т. д. Например, метод Append добавляет символы в конец объекта
StringBuilder:
Dim foo As New System.Text.StringBuilder()
foo = foo.Append("A")
foo.Appenc("hello")
' Добавляет 5 символов
foo.Append(37)
' Добавляет 2 символа
foo. Append (new Random()) '??
Как показывает последняя строка приведенного фрагмента, к объекту Stri ngBui I der можно присоединить произвольный объект. При этом VB автоматически вычисляет строковое представление объекта (а точнее, вызывает его метод ToStri ng) и присоединяет полученную строку к StringBuilder. Конечно, осмысленность строкового представления объекта зависит от реализации класса. В приведенном примере вместо случайного числа будет добавлена бесполезная строка System.Random (но команда foo. Append (New Random(). Next приведет к желаемому результату).
Метод Insert вставляет объект или значение в заданную позицию объекта
StringBuilder:
Insert(ByVal index As Integer.ByVal thing As Object)
Похожую сигнатуру имеет и метод Remove, удаляющий заданное количество символов с заданной позиции:
Remove(ByVal startlndex As Integer.ByVal length As Integer)
Перегруженные версии метода Replace выполняют несколько полезных операций:
Replace (ByVal oldValue As String. ByVal newValue As String): заменяет все вхождения старой подстроки новой подстрокой.
У этого метода существуют еще две версии, позволяющие заменить все вхождения заданной подстроки или символа в заданном фрагменте объекта Stri ngBuilder (параметр count определяет длину фрагмента):
Repliсе(ByVal oldChar As Char.ByVal newChar As Char.ByVal startlndex As Integer._ ByVal count A's Integer)
ReplacefoldValue As String.ByVal newValue As String.ByVal startlndex As Integer._ ByVal count As Integer)
В классе StringBuilder определен метод Equals, но в отличие от строк два объекта StringBuilder с одинаковым содержимым не обязательно считаются равными. Это объясняется тем, что в .NET Framework истинное выражение a.Equals(b) должно оставаться истинным всегда, а для объектов StringBuilder это невозможно, поскольку они изменяются. Использовать метод Equals для объектов StringBuilder не рекомендуется.
Метод ToStri ng преобразует объект Stri ngBui1der в String. Это делается лишь после того, как все необходимые изменения будут внесены и в дальнейшем вы собираетесь только читать содержимое4 строки.
Важнейшие члены класса
Имя
Описание
Count
Возвращает количество элементов, фактически хранящихся в массиве
GetRange
Возвращает другой объект ArrayList, содержащий последовательность смежных элементов текущего объекта
IndexOf
Возвращает индекс первого вхождения заданного элемента в динамический массив. Следует помнить, что индексация в классе ArrayList (как и в обычных массивах) начинается с нуля
Insert
Вставляет элемент в заданную позицию объекта ArrayList
InsertRange
Вставляет элементы коллекции в объект ArrayList начиная с заданной позиции
Item
Получает или задает значение элемента, находящегося в заданной позиции. Является свойством по умолчанию для класса ArrayList
LastlndexOf
Возвращает индекс последнего вхождения заданного элемента в динамический массив (индексация начинается с нуля)
Length
Возвращает количество элементов в динамическом массиве
Readonly
Возвращает новый объект ArrayList, доступный только для чтения (проверка возможности записи в динамический массив осуществляется методом IsReadOnly)
Remove
Удаляет из массива первое вхождение заданного элемента
Re move At
Удаляет элемент, находящийся в заданной позиции
RemoveRange
Удаляет последовательность смежных элементов
RepeatRange
Возвращает объект ArrayList, содержащий заданное количество дубликатов
одного элемента
Reverse
Переставляет элементы в объекте ArrayList в противоположном порядке (во всем массиве или в его части)
SetRange
Копирует элементы коллекции поверх интервала элементов ArrayList
Sort
Сортирует элементы в объекте ArrayList (во всем массиве или в его части)
ToArray
Копирует элементы из объекта ArrayList в массив
TrimToSize
Используется после завершения операций с объектом ArrayList; вместимость динамического массива уменьшается до фактического количества элементов, хранящихся в нем в настоящий момент (разумеется, позднее массив снова может увеличиться)
Среди свойств класса ArrayList наибольший интерес представляет свойство Item, которое представляет элемент с заданным индексом. Пример:
Consolе.WriteLinediiyList.Item( 1))
Свойство Item является свойством по умолчанию класса ArrayList. Это означает, что при использовании его имя может не указываться, Например, приведенная выше команда эквивалентна следующей команде:
Console. WriteLine(myList(1))
В разделе «Свойства» настоящей главы вы узнаете, чем отличаются свойства по умолчанию в VB .NET и прежних версиях VB.
В следующем коротком примере массив ArrayLi st используется для ввода и сохранения неизвестного количества строк. При этом удается обойтись без команды ReDim Preserve, необходимой при работе с обычными массивами.
Option Strict On Module Modulel
Sub Main()
Dim myList As New ArrayList()
Dim theData As String
Console.Write("Please enter each item and hit Enter key,"_
& "enter ZZZ when done:") theData =Console.ReadLine()
Do Until theData ="ZZZ" myList.Add(theData)
Console.WriteC'Please enter each item and hit Enter,"_
& "enter ZZZ when done:") theData =Console.ReadLine() Loop
Console.WriteLine("You entered "SmyList.Count() & "ITEMS.")
Console.ReadLine()
End Sub
End Module
Назад
|
Содержание
|
Вперед
|
Терминология ООП
Отправной точкой во всей терминологии ООП является понятие класса. Классом называется шаблон, по которому создаются объекты.
Каждый объект, созданный на основе класса, называется экземпляром этого класса. Методы, свойства и процедуры событий, определенные внутри класса, называются членами. Предположим, вы пишете программу для работы с информацией о сотрудниках компании. Несомненно, в такой программе будет определен класс Employee; каждый экземпляр класса Employee будет соответствовать конкретному человеку. Члены класса Employee должны соответствовать специфике решаемых задач (например, в свойстве Name будет храниться имя работника, а метод Raise-Salary будет использоваться для повышения зарплаты).
TypeName и TypeOf
Переменные, объявленные с типом Object, могут использоваться для хранения произвольных объектов. Следовательно, программисту необходимы средства для определения типа объекта, связанного с объектной переменной. В VB .NET эта задача решается двумя способами: функцией TypeName и оператором TypeOf ...Is.
Функция TypeName возвращает строку с описанием типа. Для всех типов, кроме базовых, должен быть предварительно вызван оператор New; в противном случае функция возвращает строку Nothing. Например, следующий фрагмент выводит в консольном окне строку Nothing:
Dim anSBuilder As System.Text.StringBuilder
Console.WriteLineC'My type name is " & TypeName(anSBuilder))
Но после вызова New в окне будет выведена строка StringBuilder:
Dim anSBuilder As New System.Text.StringBuilder
Console.WriteLineC'My type name is " & TypeName(anSBuilder))
Функция TypeName возвращает короткое имя класса, поэтому не рассчитывайте получить полное имя вида System.Text.StringBuilder.
Если вызвать функцию TypeName для массива, вы получите строковое имя, за которым следует пустая пара круглых скобок. Пример:
Dim aThing(5)As Integer
Console.WriteLine("My type Harness " & TypeName(aThing))
Полученная строка имеет вид Integer().
Функция TypeName удобна в процессе отладки, но в окончательных версиях программ обычно используется оператор TypeOf...Is. Он работает гораздо эффективнее, поскольку обходится без сравнений строк, необходимых при использовании TypeName. Синтаксис проверки выглядит следующим образом:
If TypeOf aThing Is System.Text.SthngBuilder Then
' Объект относится к типу StringBuilder End If
Оператор TypeOf...Is возвращает True, если объект относится к заданному типу или является производным от него. Поскольку в .NET все объекты являются производными от общего предка Object проверка вида TypeOf...Is Object всегда возвращает True, даже если переменная относится к типу, производному от Object. Если вам потребуется узнать точный тип объектной переменной, воспользуйтесь методом GetType.
Уничтожение объектов
В VB .NET объекты не умирают «естественной смертью»; в каком-то смысле они постепенно «уходят в небытие» со временем. Главное отличие от предыдущих версий VB заключается в том, что вы не можете явно освободить память, занимаемую объектом. Встроенный сборщик мусора когда-нибудь заметит, что эти блоки памяти не используются в программе, и автоматически освободит их. Автоматическая сборка мусора оказывает сильное влияние на программирование в VB .NET. В частности, сборку мусора следует рассматривать как полностью автоматизированный процесс, на который вы абсолютно не можете повлиять.
Хотя в программе можно провести принудительную сборку мусора вызовом метода System. GC. Collect(), считается, что это не соответствует хорошему стилю программирования .NET. Мы рекомендуем всегда полагаться на автоматическую сборку мусора.
Вспомните, что в прежних версиях VB в каждом классе существовало событие Termi nate, которое гарантированно вызывалось в тот момент, когда количество ссылок уменьшалось до 0 (в терминологии ООП это называется детерминированным завершением). В VB .NET (как бы вы к этому ни относились) поддерживается только недетерминированное завершение, из чего следует, что вы не можете рассчитывать на то, что некий аналог события Termi nate будет вызван в определенный момент времени. Более того, не гарантировано даже то, что он вообще будет когда-нибудь вызван!
Некоторые программисты считают Finalize аналогом события Terminate в VB .NET, однако эта аналогия неверна. Метод Finalize всего лишь содержит код, который должен выполняться при освобождении памяти вашего объекта в процессе сборки мусора. Но поскольку вы не можете явно управлять тем, когда это произойдет, мы настоятельно рекомендуем использовать Finalize лишь как дополнительную меру безопасности — например, для дублирования метода Dispose, который должен вызываться пользователем класса. Метод Dispose рассматривается ниже.
Возникает вопрос: если раньше мы проводили деинициализацию класса в событии Terminate, как же это делается сейчас? Для решения этой проблемы в VB .NET существует очень важное правило:
Если ваш класс должен освобождать какие-либо внешние ресурсы, кроме обычной памяти (например, подключения к базе данных, графические контексты, файловые манипуляторы и т.. д.), он должен содержать метод с именем Di spose, вызываемый из внешнего кода.
Мы вернемся к методу Dispose при рассмотрении интерфейса IDisposabl e в главе 5. А пока достаточно сказать, что любое графическое приложение — даже самое простое, вроде продемонстрированного в главе 1, — относится к категории программ, в которых необходим метод Dispose. Это связано с тем, что графические программы захватывают так называемые графические контексты, которые должны быть освобождены для возвращения ресурсов в систему (графические контексты не являются блоками памяти, поэтому автоматическая сборка мусора в данном случае не поможет). Теперь становится ясно, почему в автоматически сгенерированный код, приведенный в главе 1, входит вызов Dispose. Недетерминированное завершение относится к числу самых неоднозначных нововведений .NET, однако автоматическая сборка мусора является неотъемлемой частью .NET. Разработчикам при всем желании не удалось бы сохранить прежний, детерминированный вариант управления памятью и обеспечить совместимость с .NET. Кроме того, механизму, использованному в старых версиях VB (подсчет ссылок), присущи проблемы с утечкой памяти, вызванной существованием циклических ссылок, когда объект А ссылается на объект В и наоборот, как показано на Рисунок 4.8.
Такие языки, как Java, наглядно доказали, что преимущества от автоматической сборки мусора оправдывают небольшие изменения в стиле программирования, связанные с отсутствием детерминированного завершения.
Вложенные классы
В программах VB .NET нередко встречаются ситуации, когда у вас имеются два класса: «внешний» и «внутренний», фактически принадлежащий первому. Вложенные (nested) классы обычно выполняют вспомогательные функции, и их код имеет смысл лишь в контексте внешнего класса. Существует хорошее эмпирическое правило: если при просмотре внешнего класса код вложенного класса можно свернуть в окне программы и это не затруднит понимания логики внешнего класса, значит, работа вложенного класса организована правильно. Конечно, использование вложенных классов всегда приводит к некоторому нарушению инкапсуляции — вложенный класс может обращаться к закрытым членам внешнего класса (но не наоборот!). Если это обстоятельство учитывается в архитектуре вашего приложения, не стоит уделять ему особого внимания, поскольку внутренний класс всего лишь является специализированным членом внешнего класса.
VB .NET не позволяет расширять область видимости вложенного класса посредством функций. Например, открытый член внешнего класса не может вернуть экземпляр закрытого или дружественного (Friend) вложенного класса.
Введение в ООП
VB .NET значительно расширяет событийную модель программирования прежних версий VB, в основу которой были заложены элементы управления. В VB .NET вся программа состоит из самостоятельных объектов, взаимодействующих друг с другом. Объекты создаются по шаблонам, называемым классами. Объекты, построенные на базе одного класса:
не могут взаимодействовать друг с другом способами, не предусмотренными в открытом интерфейсе вашей программы;
изменяют свое текущее состояние со временем при поступлении специального запроса (в VB .NET это происходит при вызове метода или изменении свойства).
Если объект ведет себя по определенным правилам, образующим открытый интерфейс, и должным образом реагирует на внешние воздействия, пользователю не нужно думать о внутренней реализации этого объекта.
Программистам VB хорошо знакома концепция автономных фрагментов программного кода с четко определенной функциональностью. Конечно, речь идет об элементах управления. Возможность многократного использования кода, оформленного в виде элементов, повышает эффективность программирования на VB по сравнению с традиционной (процедурной) моделью.
Одно из величайших преимуществ .NET заключается в том, что вы можете программировать классы на любом языке по своему выбору, и они будут нормально работать в любом другом языке. Например, написанный на VB .NET элемент можно будет использовать в С#, и наоборот, а благодаря среде Common Language Runtime выбор языка практически не отразится на быстродействии.
Жизненный цикл объекта
Итак, при создании экземпляра класса оператором New вызывается соответствующий метод-конструктор New из определения класса (также может быть вызван общий конструктор, если он есть). Версия конструктора выбирается в соответствии с типом переданных параметров. Конструктор можно рассматривать как аналог события Class_Initiall ze в VB6.
Не в каждом классе определяется открытый конструктор. Более того, в некоторых ситуациях все конструкторы класса объявляются закрытыми и экземпляры создаются только общими методами. Конструктор объявляется закрытым в одном из следующих случаев:
Если специфика класса не предусматривает создание его экземпляров. Например, класс, состоящий только из общих членов, должен содержать только закрытые конструкторы, поскольку его экземпляры не должны создаваться во внешних программах. В подобных ситуациях вы должны определить хотя бы один закрытый конструктор, в противном случае VB .NET автоматически сгенерирует открытый безаргументный конструктор.
Если вызов закрытого конструктора через общий метод используется для контроля над созданием экземпляров. Например, если создание объекта требует больших затрат времени и ресурсов, необходимо позаботиться о том, чтобы экземпляры создавались только в случае крайней необходимости.
После того как объект будет создан оператором New, вы не сможете изменить его состояние повторным вызовом New. Пример:
Dim Tom As New EmployeeC'Tom ", 100000)
Tom = New Employee("Tom ". 125000)
В этом фрагменте создаются два разных объекта Empl oyee, причем после присваивания во второй строке первый объект Тот теряется. Иногда это соответствует намерениям программиста, иногда — нет. Например, если идентификатор работника хранится в общей переменной Empl oyeeID, то вторая строка присвоит второму объекту Тот идентификатор на 1 больше первоначального. Так или иначе, следующий фрагмент заведомо невозможен:
Dim Tom As New Employee("Tom ", 100000)
Dim Tom As New Employee("Tom ", 125000)
Компилятор выдает следующее сообщение об ошибке:
The local variable 'Tom' is defined multiple times in the same method.