Классы и объекты

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

В самом упрощенном понимании объект — это какая-то вещь или материальная сущность реального мира. Оглянитесь вокруг, и вы увидите, что наш мир состоит из бесконечного числа объектов: стол, машина, клавиатура компьютера, дом, человек и т. д. (более того, даже мир — это объект). Если приглядеться, то каждый объект имеет свойства (машина черного цвета) и операции, которые выполняются над этими свойствами (ту же машину можно перекрасить в экзотический металлик). Почти аналогично обстоит дело с объектами в абстрактном мире программ. Например, в качестве объекта можно рассматривать файл, в качестве свойства — его размер или имя, а в качестве операций — чтение или запись.

Рассмотрим еще один пример объекта в абстрактном мире программ — переменную author типа Person CO свойствами name И birthday. Итак, естЬ объект и есть свойства, что же является операцией в данном случае? Любая пользовательская процедура или функция, изменяющая свойства объекта author или просто работающая с ними. Например, процедура инициализации полей записи или процедура вывода на экран значений полей.

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

Объектно-ориентированное программирование (ООП) — наиболее популярная в настоящее время методология программирования, являющаяся развитием структурного программирования. Центральной идеей ООП является инкапсуляция, т. е. структурирование программы на модули особого вида, объединяющие данные и процедуры их обработки (вот оно, решение всех проблем!), причем внутренние данные модуля могут быть обработаны только предусмотренными для этого процедурами. В разных вариациях ООП этот модуль называют по-разному: класс, абстрактный тип данных, кластер и др. В VBA принято называть этот модуль модулем класса, или просто классом. Каждый такой класс имеет внутреннюю часть, называемую реализацией, и внешнюю — интерфейс.

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

В случае с записью Person при добавлении к ней двух процедур (persinit, persPrint) мы фактически получили класс, одним из объектов которого будет author. Доступ к свойствам и методам объекта осуществляется (как и в случае с записью) посредством точечной нотации. Например, для того, чтобы вывести на экран свойства объекта author с помощью метода Print, необходимо ввести в программу выражение author. Print ().

Помимо инкапсуляции, ООП VBA характеризуется еще одним немаловажным свойством — встраиванием. Встраивание — это механизм порождения новых классов с использованием существующих.

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

Для приложений Office XP характерна ситуация, когда необходимо указать шесть-восемь уровней вложенности, чтобы добраться до нужного объекта. Например, чтобы скрыть панель инструментов Стандартная, выполните следующий оператор присваивания:

Outlook.Application.ActiveExplorer.ShowPane (olOutlookBar ,True )

Механизм встраивания очень удобен и естественен. Прародитель семейства классов может задавать некоторые фундаментальные свойства и методы, а многочисленные потомки, имея родительские корни (свойства и методы), привносят собственные узкоспециальные свойства и методы.

Например, создавая класс Book со свойством Author класса Person, мы не только используем фундаментальные свойства и методы класса Person, но и вводим собственные свойства (title) и методы.

Наряду со встраиванием, существует еще один способ использования существующих классов при создании новых, он называется наследование. При наследовании указывается, что вновь создаваемый класс В на основе класса А содержит все (или некоторые) методы и свойства класса А, а также свои собственные методы и свойства. Например, на основе класса Person можно определить класс Author, который содержит все методы и свойства класса Person, а также свои собственные, например список книг данного автора.

Перейдем теперь от теоретического описания канонов ООП к практике их применения в VBA и начнем с синтаксиса. Синтаксически классы в VBA оформляются в виде специальных модулей классов (имя класса — это имя модуля), где в разделе Declarations помещается описание свойств (переменных) класса, а дальше идет описание методов (процедур) класса. Синтаксически описания свойств и методов практически не отличаются от описания обыкновенных переменных и процедур. Случаи отличия или особенностей употребления будут оговорены отдельно.

Давайте рассмотрим работу с классами на примере класса Person.

Пример 20.1. Создание класса Person

> Insert > Class Module

Properties Name:= Person

Программа 20.21. Создание класса

Public name As String Private birthday As Date Private male As Boolean

Public Sub perslnit( Optional persName As String = "Novikov", Optional persBirth As Date = #10/10/511, Optional persMale As Boolean = True)

name = persName birthday = persBirth male = persMale End Sub

Public Sub persPrint ()

Dim str As String

str = "Person name is " & name & Chr(13) & Ilf(male,"He","She") & " was born in " & birthday

MsgBox(str) End Sub

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

{Dim | Private | Public} имяОбъекта As Object

Если вы обратитесь к таблице встроенных типов (табл. 20.4), то увидите, что для переменных данного типа отводится 4 байта, ровно столько, сколько необходимо для хранения адреса памяти. Конечно, ссылка сама по себе бессмысленна и ее, в конце концов, необходимо будет связать с реально существующим объектом посредством оператора присваивания set, но до этого нужно объявить объект. Синтаксис объявления объектов, класс которых определен пользователем, прост:

(Dim |Private | Public | Static} имяОбъекта As [New] имяКласса

Спецификатор New указывает компилятору, что создается новый объект и под него необходимо выделить память. Использование оператора set позволяет связывать ссылку с реальным объектом:

Set ИмяОбъекта = {[New] объектноеВыраженме | Nothing}

имяОбъекта и объктноевыражение — это ссылки на объект. Таким образом, ссылке слева присваивается значение ссылки справа. Спецификатор Nothing разрывает установленное значение ссылки. Связывание с использованием оператора New называется ранним, с использованием ссылок на объект — поздним.

Рассмотрим объявление объектов класса Person. Сначала создается объект author. Далее посредством метода класса persinit инициализируются свойства объекта: имя, дата рождения и пол. После чего объявляется ссылка на объект, а потом происходит присваивание ссылке he значения ссылки author и в качестве подтверждения того, что все связалось верно, производится вывод свойств объекта he посредством метода persPrint.

Программа 20.22. Объявление объектов класса, определенного пользователем

Sub ObjectExample () Dim author As New Person Dim he As Object

author.perslnit "Novikov", #10.10.51*

Set he = author

he.persPrint End Sub

После объявления объектов, определенных пользователем, уместно рассказать об объявлении нового класса на основе уже созданного. Как мы говорили выше, для этого предусмотрен механизм встраивания объектов родительского класса в новый класс. Таким образом, объект класса родителя становится значением свойства класса потомка. Но объявление такого свойства синтаксически выглядит как объявление объекта класса. Продемонстрируем встраивание классов на примере создания нового класса Book. Для этого сначала проделаем стандартную процедуру создания нового модуля класса и присваивания ему имени Book. Потом встроим объект author класса Person И добавим СВОЙСТВО title И Два метода booklnit И bookPrint.

Метод bookinit инициализирует объект класса Book, в качестве параметров этот метод получает строку для инициализации свойства title и ссылку на объект класса Person для инициализации свойства author.

Программа 20.23. Встраивание классов

Dim author As New Person Private title As String

Public Sub booklnit (str As String, persObject As Person)

title = str

Set author = persObject End Sub

Public Sub bookPrint() Dim str As String

str = "Book title is " & title & Chr(13) & "Written by " & author.name

MsgBox(str) End Sub

Покажем использование объектов класса Book, изменив код программы 20.22 следующим образом:

Программа 20.24. Использование объектов встроенных классов

Sub ObjectExample()

Dim author As New Person

Dim bookObj As New Book

author.perslnit "Novikov", #10/10/1951#

Эта функция также возвращает ссылку на объект ActiveX. Только в этом случае в качестве параметров выступает путь, который указывает полный путь к файлу с объектом. Второй параметр при этом не обязателен.

В нижеприведенной программе мы открываем документ Word двумя разными функциями.

Программа 20.26. Открытие документа

Sub DocOpen1 ()

Dim docObj As Object

Set docObj = CreateObject ("Word.Application")

docObj.Visible = True

docObj.Documents. Add

БлокОператоров

docObj.Quit End Sub

Sub DocOpen2 ()

Dim docObj As Object

Set docObj = GetObject("d:\of200l\62\62.doc") End Sub

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

В VBA все процедуры класса делятся на три группы:

1. Процедуры-методы.

2. Процедуры-свойства.

3. Процедуры — реакции на событие.

Синтаксис объявления процедур-методов не отличается от стандартного, за исключением возможного использования ключевого слова Friend, о котором говорилось в разделе 20.4 "Структура программы". Настала пора детально рассмотреть возможность его употребления. Ключевое слово Friend, как и ключевые слова Private и Public, предназначено для ограничения области видимости метода. Если Private делает метод видимым только внутри модуля, a Public — для всех модулей всех проектов, то Friend занимает промежуточную позицию между ними, он делает видимым метод только в том проекте, где был описан класс.

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

чем меньше программе известно о реализации класса, тем лучше. То есть желательно скрыть посредством ключевого слова Private как можно большее количество свойств класса в разделе реализации, а доступ к свойствам осуществлять через специальные открытые методы (Public), организующие интерфейс класса.

Замечание

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

Например, предположим, что свойство birthday класса Person может принимать значения только в диапазоне от 1900 до 2000. Если это свойство объявлено как Public, то ему можно присвоить недопустимые значения: writer.birthday = #31/12/1899#. Ограничение видимости свойства и использование метода Public дает возможность проверки присваиваемых значений:

Public Sub InitBirthday( bthday As Date)

If bthday > #l/l/1900# and bthday < fl/1/20001 Then

birthday = bthday Else

MsgBox ("You date is out of range") End If End Sub

Таким образом, употребление ключевого слова Public при объявлении свойства name в классе Person также оказывается некорректным. Но, если отменить глобальную видимость, то станет невозможным употребление функций, напрямую связанных с глобальными свойствами. Например, метод bkPrint класса Book окажется некорректным, т. к. уже нет доступа через author.name. Как же быть? Как тогда получить значение этого свойства?

В VBA предусмотрены специальные процедуры-свойства, которые предназначены для чтения и записи закрытых свойств:

Property Let

Процедура записи позволяет присваивать свойству новые значения

Property Set

Процедура записи позволяет присваивать свойству и свойству-ссылке новые значения. Отдельная процедура нужна, поскольку свойство-ссылка'указыва-ет на объект, а присваивание объектам отличается от присваивания переменным простых типов.

Property Get

Процедура чтения (а точнее функция) позволяет считывать значение свойства. Эта процедура применима как к простым свойствам, так и к свойствам-ссылкам.

Приведем синтаксис каждой из них:

[{Public | Private | Friend}] [Static] Property Let имяПроцедурыСвойства

([списокПараметров] значение)

[блокОператоров 1}

[Exit Property]

[блокОператоров2]

End Property

Синтаксис прост и понятен. Сначала идут операторы объявления видимости процедуры-свойства и необязательное ключевое слово static. Кстати, хотя использование ключевого слова private допустимо, его присутствие сводит на нет смысл процедуры-свойства.

ИмяПроцедурыСвойства — это имя процедуры, изменяющей свойство.

Совет

Желательно, чтобы имя повторялось во всех трех процедурах-свойствах, если они имеют дело с одним и тем же свойством.

СписокПараметров ,— это список дополнительных параметров, участвующих в процедуре. Синтаксис списка параметров абсолютно такой же, как и у списка в обыкновенной процедуре. Обратите внимание на то, что все три процедуры-свойства, имеющие одно и то же имя, обязаны иметь одинаковый список параметров.

Значение — это самый важный обязательный параметр, значение которого передается свойству.

Далее идет блокОпера торов, в котором следует произвести присваивание свойству значения фактического параметра значение.

Процедура Property Set имеет следующий синтаксис.

[{Public | Private | Friend}] [Static] Property Set имяПроцедурыСвойства ([списокПараметров] ссылкаНаОбъект) [блокОператоров 1] [Exit Property] [блокОператоров2] End Property

Здесь только одно отличие от предыдущей процедуры: вместо аргумента значение стоит аргумент ссылкаНаОбъект, который представляет имя ссылки на объект, присваиваемый свойству-ссылке.

Осталось рассмотреть функцию чтения значения свойства.

[{Public | Private | Friend}] [Static] Property Get имяПроцедурыСвойства ([списокПараметров]) As ТипДанных [блокОператоров1]

[имяПроцедурыСвойства = выражение] [Exit Property] [блокОператоров2] End Property

Как видно, Property Get — это функция, и для нее действуют все синтаксические правила функций. Напомним лишь одно: тип значения, возвращаемого функцией, должен совпадать с типом значения выражение.

Рассмотрим использование процедур-свойств на нашем примере класса

Person. Поскольку МЫ объявили свойства класса name, birthday И male 3акрытыми, введем процедуры-свойства для работы с ними. В примере отображена только одна пара для свойства birthday, для name и male все строится аналогично.

Программа 20.27. Использование процедур-свойств

Public Property Get prsDateO As Date

prsDate = birthday End Property

Public Property Let prsDate(bthday As Date)

If bthday > #1/1/19001 And bthday < #1/1/20001 Then

birthday = bthday Else

MsgBox ("Incorrect date") End'If End Property

Но вернемся к методу bkPrint класса Book. Сейчас он не работает, поскольку обращение author.name стало бессмысленным. Для правильной работы метода необходимо заменить строку с неправильным оператором на следующую:

str = "Book title is " & title & Chr(13) & _ "Written by " & author.prsName

Таким образом, дойдя до данной строки кода, программа выполнит процедуру-свойство объекта author, т. е. prsName, которая возвращает текущее значения свойства name.

После описания процедур-свойств перейдем к заключительному аккорду в рассмотрении процедур класса: к процедурам реакции на событие. Выше мы уже упоминали о них. Напомним, что тогда мы приводили пример процедуры реакции на событие Document_ciose (реакция на событие "Закрыть документ"). Как мы упоминали, существует множество событий и множество процедур реакции на них. Но помимо встроенных процедур, пользователь вправе создавать собственные обработчики событий.

Чтобы пояснить сказанное, рассмотрим типовой сценарий жизни объекта от объявления до удаления:

1. Вход в процедуру для работы с объектом.

2. Создание ссылки на объект.

3. Создание нового объекта.

4. Работа с объектом.

5. Выход из процедуры.

Прокомментируем этот сценарий. Для человеческого глаза нет ничего особенного в этой быстротечной жизни, а для системного "всевидящего ока" произошло два очень важных события: создание объекта и его удаление (при выходе из процедуры). Более того, мы не заметили, как были выполнены две процедуры реакции на эти события: initialize и Terminate, которые, соответственно, проинициализировали и уничтожили объект. Рассмотрим их подробнее.

Когда создается объект, вызывается процедура initialize, которая всем свойствам объекта присваивает значения, определенные для них по умолчанию. Данная процедура называется конструктором. Пользователь вправе изменить эту процедуру, указав, как инициализировать свойства объекта при его непосредственном объявлении.

Когда же объект выходит из области своего действия, например из процедуры, в которой он был локально объя-влен, автоматически вызывается процедура Terminate, которая освобождает память, занимаемую объектом и ссылкой на него. Эту процедуру называют деструктором. Пользователь вправе и здесь изменить процедуру, но на практике этой возможностью пользуютсяо-чень редко.

Покажем использование процедур реакции на событие на примере класса Person. Мы ввели конструктор, инициализирующий свойства name, birthday И male.

Программа 20.28. Объявление процедур реакций на событие

Private Sub Class_Initialize()

name = "Novikov"

birthday = #10/10/19511

male = True End Sub

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

Поздравляем: класс полностью готов к работе! Теперь вы можете использовать его возможности на полную мощность и ощутить всю прелесть ООП.

Попытайтесь самостоятельно встроить в наш класс Person еще несколько свойств и методов, а потом реализовать их.

На этом мы заканчиваем описание классов в VBA. Надеемся, что все описанные выше конструкции и примеры достаточно просты и понятны. Единственное, чему еще хотелось бы уделить особое внимание, — это семейство (collection), структура (класс) VBA, стоящая несколько в стороне от канонического описания, но тем не менее очень важная при реализации ряда задач, а также очень часто используемая в объектной модели Microsoft Office XP.

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

Для решения подобной проблемы в VBA присутствует особый класс collection, который позволяет очень быстро и удобно решить проблему построения динамических структур данных.

Семейством (Collection) называется упорядоченный набор объектов. В принципе объекты могут быть разных классов, но, как правило, встречаются семейства однородных объектов. Грубо говоря, семейство представляет собой сплав динамического массива и записи, что позволяет, с одной стороны, перенумеровать все элементы семейства, а с другой, иметь прямой доступ к объектам — элементам семейства — по значению определенного поля, называемого ключом. Ключ — это строковое выражение, которое может быть использовано вместо индекса для доступа к элементу семейства.

Как же устроен этот чудо-класс? Класс collection имеет одно свойство

Count и три метода — Add, Item и Remove.

Свойство count очень простое, оно возвращает количество элементов семейства (то есть количество объектов, включенных в семейство в данный момент).

Метод Add (элемент (, ключ] [, до] [, после}) добавляет объект в семей-ство. Его обязательным аргументом является элемент. Он, как вы можете догадаться, добавляет в семейство элемент. Параметр ключ задает ключ, по которому можно будет произвести поиск этого элемента. Параметры до и после указывают на то, перед каким или после какого элемента добавляется новый. По умолчанию элемент добавляется в конец семейства.

Метод Remove (ключ) удаляет элемент из семейства. Параметр ключ — это ключ или индекс, указывающий на удаляемый элемент. Заметьте, что при удалении элемента из семейства не остается дыр : индексы перенумеровываются, значение свойства count уменьшается на единицу.

Метод item (ключ) возвращает значение элемента семейства с ключом ключ. Как в случае с методом Remove, параметр ключ может быть как ключом, так и индексом.

Давайте запрограммируем решение вышерассмотренной задачи, используя знания о семействе. В этом примере мы объявляем семейство Books и инициализируем его, добавляя три объекта. После того как семейство инициализировано, мы удаляем один элемент и выводим оставшиеся элементы.

Программа 20.29. Объявление семейства

Sub BooksCollection()

Dim Books As New Collection

Dim Novikov As New Person

Dim Vba As New Book

Novikov.perslnit "Novikov", #10/10/1951#, True

Vba.booklnit "VBA и разработка приложений в Office 2000", Novikov

Books.Add Vba, "VBprog"

Dim Office As New Book

Office.booklnit "Microsoft Office 2001 в целом", Novikov

Books.Add Office, "off"

Dim Stroustrup As New Person

Dim С As New Book

Stroustrup.perslnit "Stroustrup", #11/25/1947#

C.booklnit "C++ Programming", Stroustrup

Books.Add C, "Cprog", 2

Books.Remove "Cprog" For i = 1 To Books.Count

Books.Item(i).bookPrint Next i End Sub

На этом мы заканчиваем описание языка VBA, считая, что вы готовы к его полноценному использованию, и переходим к описанию редактора Visual Basic Editor.