Переход к использованию объектов

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

  1. Задача разбивалась на подзадачи; те, в свою очередь, делились на подзадачи следующего уровня и т. д. Это продолжалось до тех пор, пока упрощение подзадач не позволяло реализовать их непосредственно (подход «сверху вниз»).
  2. Программист писал процедуры для решения простых задач и последовательно объединял их в более сложные процедуры, пока недобивался нужного эффекта (подход «снизу вверх»).

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

Между ООП и процедурно-ориентированным программированием существуют два важных различия:

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

Возникает очевидный вопрос: по каким критериям выделять классы в программе? Для этого имеется хорошее эмпирическое правило, которое связывает компоненты объектной модели с частями речи. Классы соответствуют существительным в постановке задачи. В нашем примере центральное место занимает существительное «работник» (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).

 

Экземпляры

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

  1. Каким состоянием должен обладать объект?
  2. Какими отличительными особенностями должен обладать объект?
  3. Каким должно быть поведение объекта?

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

Текущее состояние объекта не обеспечивает его однозначной идентификации. Два объекта могут находиться в одинаковом состоянии, одинаково реагировать на внешние воздействия, но при этом они все равно остаются разными объектами (как два массива с одинаковым содержимым). Таким образом, должен существовать некий критерий, по которому объект можно отличить от других похожих объектов. К поведению объекта относится информация о том, что он делает в данный момент и что он теоретически может сделать в будущем. В VB .NET поведение объекта определяется его свойствами, методами и событиями.

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

 

Преимущества ООП

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

Класс представляет собой шаблон для создания объектов, состояние которых изменяется со временем.

Выглядит слишком абстрактно? И вроде бы не имеет никакого отношения к программированию VB? Вспомните панель элементов Visual Basic. В прежних версиях VB каждая кнопка панели создавала объект, являющийся экземпляром класса соответствующего элемента.

А если бы панель элементов, готовая в любой момент создать новое текстовое поле или кнопку по вашему запросу, куда-то исчезла? Только представьте, какими сложными станут программы VB, если каждое текстовое поле придется оформлять в виде отдельного модуля! Кстати говоря, один модуль нельзя подключить к программе дважды, поэтому создание формы с двумя одинаковыми текстовыми полями потребует довольно изощренного программирования.

Благодаря существованию панели элементов VB всегда был объектно-ориентированным языком. Начиная с версии 4 в нем появилась возможность создавать некоторые типы объектов. Но только в VB .NET программист может определять классы для любых объектов и в полной мере использовать средства ООП на том же уровне, что и в C++ и С#. Более того, все языки .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).

Рис. 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)

Не используйте класс Random для серьезных криптографических задач, поскольку его алгоритм построения случайных чисел легко взломать. Библиотека .NET Framework велика, и в нее входит генератор случайных чисел, надежный с точки зрения криптографии (конечно, более медленный) — класс RandomNumberGenerator из пространства имен System.Security.Cryptography (пространства имен рассматриваются ниже в этой главе).

Доступ к средствам класса обычно осуществляется через конкретный экземпляр, однако у этого правила имеется исключение. Дело в том, что некоторые возможности реализуются на уровне класса, а не отдельных объектов. В главе 3 мы встречались с классом Math и использованием конструкций Math . PI и Math . Sin( ) без вызова метода New. Члены, принадлежащие классу в целом, а не его отдельным экземплярам, называются общими (shared). К общим членам можно обращаться как по имени класса, так и по имени объектной переменной, объявленной с соответствующим типом. Предположим, у вас имеется класс Ваr с общим методом Foo. Метод Foo может быть вызван любым. из приведенных ниже способов:

Ваг.Foo()

Dim test As Bar test.Foo()

В других языках программирования (таких, как С# и Java) общие члены называются статическими (static).

 

Параметризованные конструкторы

На первый взгляд конструктор 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, но для любых функций и процедур. Перегрузка также используется для решения проблемы с передачей необязательных параметров.