| « Поставить закладку » « Сделать стартовой » | |||
|
|||
|
Глава 4. Классы и объекты
Глава №4. Классы и объекты Эта глава вместе
с главами 5 и 6 занимает центральное место в книге. Причины просты: язык VB .NET
стал первой объектно-ориентированной версией VB, и тот, кто недостаточно хорошо
разбирается в ООП, не сможет в полной мере использовать новые возможности VB
.NET. Вероятно, это утверждение вас удивило — ведь возможность определения
классов и создания объектов на их основе появилась еще в VB4. Чем же VB .NET так
отличается от своих предшественников, что вам придется полностью переосмыслить
свой стиль программирования? Развернутый ответ на этот вопрос приводится в этих
трех главах. За время
преподавательской работы у нас сложилось впечатление, что большинство
программистов VB почти не использовали средства ООП в предыдущих версиях Visual
Basic. Это объяснялось как неудобной и плохо проработанной реализацией ООП, так
и тем, что многие программисты не понимали, как правильно применять средства
объектно-ориентированного программирования. Усовершенствования в VB .NET
покончили с первой проблемой. Что касается второй... Что же, именно поэтому эта
глава начинается с краткого курса ООП. Как бы вы к этому не относились,
нормальное программирование в VB .NET возможно лишь при полноценном
использовании объектной природы этого языка. В главах 4-6 ООП рассматривается с
практической точки зрения программиста VB .NET, без углубленного изучения
теоретических тонкостей. Особое внимание уделяется практическим примерам и
приемам, используемым при решении реальных задач. Мы постараемся обойтись без
высоких материй, поскольку чрезмерное обилие деталей только отвлечет читателя от
основной темы — если вас интересует теория, вы найдете ее в десятках книг,
посвященных ООП. Тем не менее в ООП используется достаточно специфическая
терминология, поэтому мы начнем с обсуждения важнейших концепций и терминов
(читатели, хорошо разбирающиеся в ООП, могут пропустить вводную
часть). На следующем
этапе рассматривается использование существующих классов и объектов, созданных
на их основе. В частности, мы покажем, как использовать важный класс
StringBuilder и некоторые новые классы коллекций .NET Framework (в том числе
хэш-таблицы и динамические массивы). Вы узнаете о некоторых тонкостях, связанных с передачей
объектов функциям и процедурам. Только после этого мы перейдем к определению
новых классов и построению объектов на их основе (хотя в этой главе приводятся
начальные сведения о наследовании и интерфейсах, подробное описание этих
ключевых концепций ООП откладывается до главы 5).
VB .NET
значительно расширяет событийную модель программирования прежних версий VB, в
основу которой были заложены элементы управления. В VB .NET вся программа
состоит из самостоятельных объектов, взаимодействующих друг с другом. Объекты
создаются по шаблонам, называемым классами. Объекты, построенные на базе
одного класса:
Если объект
ведет себя по определенным правилам, образующим открытый интерфейс, и
должным образом реагирует на внешние воздействия, пользователю не нужно думать о
внутренней реализации этого объекта.
Одно из
величайших преимуществ .NET заключается в том, что вы можете программировать
классы на любом языке по своему выбору, и они будут нормально работать в любом
другом языке. Например, написанный на VB .NET элемент можно будет использовать в
С#, и наоборот, а благодаря среде Common Language Runtime выбор языка
практически не отразится на быстродействии. Классы как пользовательские типы Класс также
можно рассматривать как «интеллектуальный» пользовательский тип, обладающий
расширенными возможностями — например, перед изменением своих внутренних данных
класс может проверить их новые значения. При обращении к данным класс может
проверить полномочия стороны, от которой поступил вызов. Наконец, представьте
себе пользовательский тип, который возвращает данные в определенном формате
вместо того, чтобы просто выдавать их внутреннее представление. С этой точки
зрения объект представляет собой переменную пользовательского типа, с которой
ассоциированы функции обращения к данным и их проверки. Главный принцип работы с
данными класса — замена прямого доступа к данным вызовом вспомогательных
функций.
По мере освоения
ООП перед вами откроются новые возможности, не ограничивающиеся простой
проверкой или возвращением внутренних данных объекта. Как объекты взаимодействуют друг с другом? Согласно одному
из ключевых принципов ООП каждый класс (= шаблон для построения объектов)
отвечает за выполнение небольшой группы взаимосвязанных задач. Проектирование и
отладка специализированного класса, предназначенного для создания простых
объектов с небольшим количеством задач, занимает гораздо меньше времени, чем
разработка классов со сложными внутренними структурами данных, многочисленными
методами и свойствами. Если потребуется сделать нечто такое, на что ваш объект
не рассчитан, не стоит вносить изменения в исходный класс и усложнять его —
лучше определите новый класс, оптимизированный для решения этой задачи.
Предоставьте старому объекту доступ к объектам нового типа, и старый объект
сможет обратиться к новому объекту с запросом на выполнение нужной
операции. Пользователи
прежних версий VB хорошо знакомы с подобным взаимодействием между объектами. В
частности, именно это и происходит при вызове методов или задании свойств
элементов. С точки зрения пользователя, запрос к другому объекту реализуется
обращением к свойству или вызовом метода этого объекта. На внутреннем уровне в
обработке запросов участвуют обобщенные версии функций и процедур VB .NET (см.
главу 3). Таким образом, все подробности внутренней реализации остаются скрытыми
от внешнего мира. На жаргоне ООП
этот принцип формулируется так: объекты взаимодействуют с другими объектами
только посредством обмена сообщениями. Еще раз подчеркнем — объект
никогда не должен напрямую работать с внутренними данными другого
объекта. Все взаимодействие должно происходить на уровне сообщений (задания
свойств и вызова методов). Проектируйте свои объекты по принципу «черного
ящика»: объект реагирует на сообщения из определенного набора, а его фактическая
реализация остается неизвестной. Подведем итог.
Все операции с объектами в программе должны сводиться к изменению их свойств и
вызову методов. Не используйте открытые (глобальные) переменные в классах или
объектах, созданных на их основе. Отправной точкой
во всей терминологии ООП является понятие класса. Классом называется
шаблон, по которому создаются объекты. Каждый объект,
созданный на основе класса, называется экземпляром этого класса. Методы,
свойства и процедуры событий, определенные внутри класса, называются членами.
Предположим, вы пишете программу для работы с информацией о сотрудниках
компании. Несомненно, в такой программе будет определен класс Employee; каждый
экземпляр класса Employee будет соответствовать конкретному человеку. Члены
класса Employee должны соответствовать специфике решаемых задач (например, в
свойстве Name будет храниться имя работника, а метод Raise-Salary будет
использоваться для повышения зарплаты). Отношения между классами в программах В традиционном ООП предусмотрены три типа отношений между классами:
Вернемся к
классической тройке. Отношение использования, самое очевидное и
распространенное, всего лишь означает, что один класс зависит от другого. Во
всех ситуациях, когда один объект посылает сообщение другому объекту, можно
говорить о зависимости между этими объектами. В обобщенном случае класс А
использует класс Б, если:
или
Термин
«включение» (агрегирование) означает, что объект класса А содержит внутренние
объекты класса Б. На базе
включения реализуется методика делегирования, когда поставленная перед
внешним объектом задача перепоручается внутреннему объекту, специализирующемуся
на решении задач такого рода. Агрегирование с делегированием методов было очень
распространенным явлением в прежних версиях VB, поскольку этот принцип использовался при
создании новых элементов (вспомните, как создавались новые, специализированные
текстовые поля — вы размещали текстовое иоле внутри формы пользовательского
элемента, а затем запускали программу-мастер, которая автоматически генерировала
код делегирования). Агрегирование
по-прежнему широко используется в VB .NET, но во многих ситуациях ему на смену
приходит наследование — третий тип отношений между классами. Наследование
считается одним из четырех «краеугольных камней» ООП наряду с абстракцией,
инкапсуляцией и полиморфизмом. Все четыре концепции будут рассмотрены в
ближайших четырех разделах. Абстракцией
называется моделирование объектов в программе. Другими словами, речь идет об
имитации реально существующих объектов, отражающей особенности их взаимодействия
в окружающем мире. Так, первый объектно-ориентированный язык Simula (http://java.sun.com/people/jag/SimulaHistory.html)
разрабатывался специально для задач имитации и моделирования. Впрочем, модные
концепции виртуальной реальности выводят принцип абстракции на совершенно новый
уровень, не связанный с физическими объектами. Абстракция необходима, потому что
успешное использование ООП возможно лишь в том случае, если вы сможете
выделить содержательные аспекты своей проблемы. Приступая к
построению объектной модели, всегда задавайте себе вопрос: какие свойства и
методы должны входить в объект, чтобы он адекватно моделировал ситуацию для
решения поставленной задачи? В ООП термин
«инкапсуляция» означает то, что мы обычно называем маскировкой данных.
Скрывая данные, вы определяете свойства и методы для работы с ними. Вспомните,
что говорилось выше, — успешное применение ООП возможно лишь в том случае, если
все операции с внутренними данными объекта осуществляются посредством обмена
сообщениями. Данные объекта хранятся в полях экземпляра; также часто
встречается термин «переменные экземпляра». В сущности, это одно и то же,
и выбор зависит в основном от того, к какому термину вы привыкли; в этой книге
обычно используется термин «поля экземпляра». Текущее состояние объекта
определяется текущими значениями полей экземпляра. Не забывайте главное правило:
никогда не предоставляйте прямой доступ извне к полям экземпляра (внутренним
данным объекта). Вернемся к
примеру с объектно-ориентированной программой для отдела кадров, в которой мы
определили класс Employee. В переменных класса Еmplоуее могут храниться
следующие сведения:
Чтобы изменить
значения полей экземпляра, пользователи не обращаются к ним напрямую, а изменяют
свойства и вызывают методы типа Rai seSalаrу. Разумеется, метод RaiseSalary
будет изменять поле с текущей зарплатой, но в нетривиальном классе Employee он
может работать с несколькими полями. Например, легко представить себе метод Rai
seSalагу, который принимает решение о повышении зарплаты с учетом ее текущего
уровня, рабочего стажа и личных достижений работника. Подведем итог.
Инкапсуляция определяет функциональность объекта с точки зрения пользователя.
Ее непосредственными проявлениями в VB .NETвыступают члены класса (методы,
события и свойства).
В качестве
примера наследования представьте себе классы для отдельных категорий работников
(класс Programmer, класс Manager и т. д.). Механизм, используемый для создания
таких классов на базе класса Empl oyee, называется наследованием. В
иерархии наследования класс Employee называется базовым, а класс
Programmer — производным классом. Производные классы:
Например, метод
RaiseSalary класса Manager может давать менеджеру большую прибавку к жалованию,
чем метод RaiseSalary класса Programmer, при одинаковом стаже и личных
показателях. Производный
класс может содержать новые методы, не имеющие аналогов в базовом
классе. Например, в
класс Manager может быть включено новое свойство Secretary. Разработчики
давно хотели видеть наследование в VB и громко жаловались на его отсутствие.
Нельзя сказать, что шум был поднят на пустом месте, однако многие склонны
переоценивать важность наследования. Дело в том, что наследование, если
хорошенько разобраться, всего лишь избавляет программиста от необходимости
заново писать готовый код. В наследовании нет никакой мистики — это лишь способ
упростить повторное использование программного кода. В нашем примере классы
Employee и Manager обладали рядом сходных черт (наличие даты найма, зарплаты и
т. д.). Зачем программировать свойство Salary в двух местах, если код будет
абсолютно одинаковым? При полноценной реализации наследования использование
функциональности базового класса в производном классе практически не требует
дополнительных усилий — производный класс изначально наследует все члены своего
предка. Программист может переопределить некоторые члены базового класса в
соответствии со спецификой производного класса. Например, если менеджер
автоматически получает 8-процентную прибавку к зарплате, тогда как для
большинства работников прибавка составляет всего 4%, метод RaiseSalагу класса
Manager должен заменить метод RaiseSalary базового класса Employee. С другой
стороны, методы вроде GetName в изменении не нуждаются и остаются в прежнем
виде.
Напоследок мы
хотим предупредить: не используйте наследование, если вы твердо не уверены в
существовании логической связи «является частным случаем». Например, не
создавайте класс Contractor (внештатный работник), производный от Employee,
только для того, чтобы избавиться от хлопот по дублированию кода свойств имени
или номера социального страхования. Внештатный работник не является
служащим компании, и бухгалтер, простоты ради оформивший его по общим
правилам, только наживет себе неприятности с налоговой инспекцией. То же
относится и к вам: применение наследования при отсутствии логической связи
«является частным случаем» приведет к печальным последствиям (см. главу
5). В традиционной
трактовке термин «полиморфизм» (от греческого «много форм») означает, что
объекты производных классов выбирают используемую версию метода в зависимости от
своего положения в иерархии наследования. Например, и в базовом классе Employee,
и в производном классе Manager присутствует метод для повышения зарплаты
работника. Тем не менее метод RaiseSalаrу для объектов класса Manager работает
не так, как одноименный метод базового объекта Employee. Классическое
проявление полиморфизма при работе с классом Manager, производным от Empl oyee,
заключается в том, что при вызове метода по ссылке на Empl oyee будет
автоматически выбрана нужная версия метода (базового или производного класса).
Допустим, в программе метод RaiseSalary вызывается по ссылке на
Employee.
В обоих случаях
объект выбирает метод в зависимости от полученного сообщения. При отправке
сообщения не нужно знать, к какому классу фактически принадлежит объект; достаточно
разослать сообщение всем объектам 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, выбирающих нужное
действие в зависимости от типа объекта. Переход к использованию объектов С давних времен
в программировании использовалась структурная, процедурно-ориентированная
модель. Сначала программист разбирался, что должна делать программа, а затем
выбирал одно из двух:
Конечно, многие
опытные программисты не следовали рекомендациям теоретиков, выступавших за
первый способ, и предпочитали решать практические задачи комбинацией этих двух
стратегий [В программировании это обычно называется встречным
движением.]. Между ООП и
процедурно-ориентированным программированием существуют два важных
различия:
Возникает
очевидный вопрос: по каким критериям выделять классы в программе? Для этого
имеется хорошее эмпирическое правило, которое связывает компоненты объектной
модели с частями речи. Классы соответствуют существительным в постановке задачи.
В нашем примере центральное место занимает существительное «работник»
(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). Итак, вы решили,
какие классы должны входить в ваш проект. На следующем этапе построения
объектной модели рассматриваются конкретные экземпляры этих классов. Попробуйте
ответить на три вопроса:
В полях
экземпляра хранится информация об операциях, выполняемых объектом; некоторые
поля могут использоваться для хранения информации об истории существования
объекта. В совокупности эти сведения определяют состояние объекта.
Состояние объекта подвержено частым изменениям, но, как было сказано выше, любые
изменения в состоянии объекта в результате внешних воздействий должны
происходить только в результате полученных сообщений. Текущее
состояние объекта не обеспечивает его однозначной идентификации. Два объекта
могут находиться в одинаковом состоянии, одинаково реагировать на внешние
воздействия, но при этом они все равно остаются разными объектами (как два
массива с одинаковым содержимым). Таким образом, должен существовать некий
критерий, по которому объект можно отличить от других похожих объектов. К
поведению объекта относится информация о том, что он делает в данный момент и
что он теоретически может сделать в будущем. В VB .NET поведение объекта
определяется его свойствами, методами и событиями. Несомненно, эти
три характеристики влияют друг на друга, и это обстоятельство должно учитываться
в коде класса. Например, поведение объекта зависит от его текущего состояния:
заблокированное текстовое поле ведет себя совсем не так, как доступное, и об
этом следует помнить при проектировании класса. На первый взгляд
классы ООП и связанные с ними методы и свойства имеют много общего с процедурным
подходом и модульным строением программ. Ключевое различие заключается в
следующем: Класс
представляет собой шаблон для создания объектов, состояние которых изменяется со
временем. Выглядит слишком
абстрактно? И вроде бы не имеет никакого отношения к программированию VB?
Вспомните панель элементов Visual Basic. В прежних версиях VB каждая кнопка
панели создавала объект, являющийся экземпляром класса соответствующего
элемента. А если бы панель
элементов, готовая в любой момент создать новое текстовое поле или кнопку по
вашему запросу, куда-то исчезла? Только представьте, какими сложными станут
программы VB, если каждое текстовое поле придется оформлять в виде отдельного
модуля! Кстати говоря, один модуль нельзя подключить к программе дважды, поэтому
создание формы с двумя одинаковыми текстовыми полями потребует довольно
изощренного программирования. Благодаря
существованию панели элементов VB всегда был объектно-ориентированным языком.
Начиная с версии 4 в нем появилась возможность создавать некоторые типы
объектов. Но только в VB .NET программист может определять классы для любых
объектов и в полной мере использовать средства ООП на том же уровне, что и в C++ и С#. Более
того, все языки .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 эта особенность была
исключена. Некоторые
программисты (особенно работающие на С# и 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).
Рис. 4.1.
Подсказка IntelliSense для класса Random Например, в
отличие от функции Rnd из прежних версий VB вам не придется дополнительно
обрабатывать числа от 0 до 1, чтобы получить случайное положительное число в
заданном интервале. Вам понадобилось случайное целое от 1 до 6? Воспользуйтесь
следующей конструкцией: Dim aRandomlinstance As New Random() Dim die As Integer die =aRandomInstance.Next(1.6) Console.WriteLine(die)
Доступ к
средствам класса обычно осуществляется через конкретный экземпляр, однако у
этого правила имеется исключение. Дело в том, что некоторые возможности
реализуются на уровне класса, а не отдельных объектов. В главе 3 мы встречались
с классом Math и использованием конструкций Math . PI и Math . Sin( ) без вызова
метода New. Члены, принадлежащие классу в целом, а не его отдельным экземплярам,
называются общими (shared). К общим членам можно обращаться как по имени
класса, так и по имени объектной переменной, объявленной с
соответствующим типом.
Предположим, у вас имеется класс Ваr с общим методом Foo. Метод Foo может быть
вызван любым. из приведенных ниже способов: Ваг.Foo() Dim test As Bar
test.Foo()
Параметризованные конструкторы На первый взгляд
конструктор New работает так же, как в предыдущих версиях VB. В действительности
изменилось очень многое, и самое принципиальное изменение заключается в том, что
при вызове New теперь могут передаваться параметры. Как вы вскоре увидите, в
пользовательских классах переопределенная версия New замещает событие Initial
ize из прежних версий VB, которое не поддерживало параметров. Например, для
класса Random определены две версии конструктора. Первая версия вызывается без
параметров, как показано выше. В этом случае вы получаете случайные числа,
полученные в результате случайной инициализации генератора по показаниям
системных часов. Другая версия выглядит так: Dim aRandomlnstance As Random aRandomlnstance =
New Random(42) Эта версия
класса Random генерирует одну и ту же последовательность случайных чисел,
начинающуюся с числа 42 (эта возможность абсолютно необходима в процессе
отладки). Как ни странно,
появление параметризованных конструкторов в VB сделало для полноценной
реализации ООП едва ли не больше, чем поддержка наследования. Если наследование
еще можно заменить в программе другими средствами (обычно агрегированием), то
компенсировать отсутствие параметризованных конструкторов гораздо труднее.
Параметризованные конструкторы нужны прежде всего для того, чтобы предотвратить
случайное создание объекта в неопределенном состоянии. В прежних версиях VB это
всегда порождало массу проблем, поскольку событие Initialize вызывалось без
параметров. Оставалось лишь следовать общепринятой схеме — включать в класс
функцию инициализации объектов (обычно этой функции присваивалось имя Create) и
надеяться на то, что пользовать класса не забудет вызвать эту функцию. В
противном случае объект не инициализировался, а поля экземпляра сохраняли
значения по умолчанию, что приводило к появлению тонких, неуловимых
ошибок. В VB .NET, как
во всех объектно-ориентированных языках, объект создается только конструктором.
Более того, ниже будет показано, как потребовать обязательной передачи
параметров при вызове конструктора — это гарантирует, что объект не будет создан
в неопределенном состоянии.
Другим хорошим примером класса с несколькими конструкторами является класс 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,
который получает массив символов и преобразует их в строку.
Чрезвычайно
полезный класс 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. Таблица
4.1. Конструкторы класса StringBuilder
В следующем
фрагменте показано, как быстро создать строку, состоящую из 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 выполняют несколько полезных операций:
У этого метода
существуют еще две версии, позволяющие заменить все вхождения заданной подстроки
или символа в заданном фрагменте объекта 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)
Метод ToStri ng
преобразует объект Stri ngBui1der в String. Это делается лишь после того, как
все необходимые изменения будут внесены и в дальнейшем вы собираетесь только
читать содержимое4 строки. Хронометраж — насколько быстрее работает класс
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 должен существовать какой-либо способ логической
группировки информации. В .NET эта задача решается при помощи пространств
имен. Конечно, вы можете создавать собственные пространства имен; ниже в
этой главе мы покажем, как это делается. В каждом городе есть своя Главная Улица, а в каждой библиотеке непременно найдется метод с именем Open. Пространства имен позволяют различать эти методы. Например, в пространстве имен System. 10 собраны методы для выполнения файловых операций; в него входит класс Fil е, а в этом классе имеется метод Open. Полное имя метода выглядит так: System.I0.File.Open Класс File
является частью пространства имен System. I0, поэтому он не конфликтует с другим
классом File из пространства имен Cornell .Morrison.NiftyClasses, который также
может содержать собственный метод Open. Обращаясь к
хорошим знакомым, мы не называем их по имени-отчеству и фамилии. В .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).
Рис. 4.2.
Страница Imports окна свойств решения
Рис. 4.3.
Пространство имен Microsoft.VisualBasic в программе Object
Browser Команда Imports
не увеличивает объем программы, поскольку она не включает весь код пространства
имен в проект, а просто упрощает ссылки на члены классов, входящих в
пространство имен. На скорость работы она тоже не влияет. Импортируются только
пространства имен, входящие в сборки, ссылки на которые были включены в
программу командой Project > Add Reference (сборки рассматриваются в главе
13).
Если два
импортированных пространства имен содержат классы с одинаковыми именами, то для
различения этих классов вам придется указывать их полные имена (VB .NET не
позволяет создать два одноименных класса в одном пространстве
имен). Особая версия
команды Imports используется для предотвращения потенциальных конфликтов имен с
ранее импортированными классами. Например, если вы захотите работать на уровне
совместимости с прежними версиями Visual Basic (чего делать не рекомендуется), в
программе почти наверняка возникнут конфликты имен. Включите в программу ссылку
на уровень совместимости VB, а затем воспользуйтесь командой
вида: Imports VB6Stuff =
Microsoft.VisualBasic.Compatibility.VB6 После этого
остается лишь начинать все ссылки на уровень совместимости с префикса
«VBSStuff.», и все проблемы с конфликтами имен исчезнут. Из пространства
имен нельзя импортировать отдельный класс, чтобы упростить ввод имен его членов.
Предположим, вы хотите импортировать класс DirectoryInfo, чтобы упростить ввод
имен его членов. Но следующая команда недопустима: Imports
System.IO.Directorylnfo Чтобы рассмотреть пример использования 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.
Справочная система и .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), рассматриваются
далее в этой главе, Классы коллекций в .NET Framework Чтобы пробудить
в вас интерес к .NET Framework, мы кратко рассмотрим некоторые классы коллекций.
В этих классах реализуются стандартные структуры данных, часто используемые в
нетривиальных программах. Коллекции настолько важны, что они по умолчанию
автоматически импортируются в каждое решение VB .NET (в пространстве имен
System.Collections).
Рис. 4.4.
Пространство имен System.10 в справочной системе В совокупности
эти классы оставляют далеко позади примитивный класс Collection из VB6. Самые
полезные классы коллекций перечислены в табл. 4.2. В следующих двух разделах
рассматриваются основные принципы работы с двумя важнейшими классами: ArrayList
и HashTable. Знакомство с очень важным классом Dictio-naryBase откладывается до
следующей главы, посвященной наследованию. Таблица
4.2. Основные классы коллекций
Рис. 4.5.
Класс DirectoryInfo в справочной системе Класс ArrayList
реализует динамический массив, размеры которого автоматически
увеличиваются и уменьшаются по мере надобности. Динамические массивы работают
чуть медленнее обычных массивов, но они заметно упрощают многие задачи
программирования. Кроме того, в отличие от большинства массивов класс ArrayLi st
является гетерогенным, то есть позволяет хранить объекты разных типов. В главе 5
будет показано, как создать класс ArrayList для хранения объектов лишь одного
типа; вы также узнаете о некоторых нюансах, связанных с хранением обобщенных
объектов в ArrayLi St. Использование
ArrayList вместо базового массива означает, что вам не придется часто вызывать
ReDim Preserve для сохранения существующих данных. Достаточно вызвать метод Add,
и класс ArrayList сам выполнит всю черновую работу. Класс ArrayList содержит ряд
других полезных методов. Например, метод AddRange позволяет перенести в
динамический массив все содержимое существующего массива всего одной командой.
После завершения обработки элементы можно скопировать обратно. В частности, это
позволяет легко объединить содержимое двух массивов. В табл. 4.3 перечислены
основные члены класса ArrayList (полный список приведен в электронной
документации).
Рис. 4.6. Метод GetDirectories в справочной системе Таблица
4.3. Важнейшие члены класса ArrayList
|