Объектно-ориентированное программирование

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

Основные понятия

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

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

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

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

К сожалению не все концепции ООП реализованы в языке VBA. Если вы программировали на других языках, например, на C++, то заметите, что полностью отсутствует такое важное понятие как наследование (возможность создания производных классов на основе базового класса), а также не реализована перегрузка операторов. Все это значительно ограничивает возможности языка VBA.

Создание класса

Код класса необходимо размещать в специальном модуле — модуле класса. Создать модуль класса можно несколькими способами:

В результате будет создан модуль и открыт в отдельном окне. Изменить название класса можно в окне Properties Window (если окно не отображается, то из меню View следует выбрать пункт Properties Window или нажать клавишу <F4>). Находим в таблице свойство Name и делаем двойной щелчок на ячейке справа от свойства. Ячейка станет доступной для редактирования. В данный момент в ячейке находится значение Class1. Изменим название по умолчанию на пользовательское. Для этого в ячейку вводим, например, название MyClass.

Отобразить содержимое существующего модуля класса можно из окна Project Explorer. Для этого нужно сделать двойной щелчок на ярлыке модуля или щелкнуть правой кнопкой мыши на ярлыке модуля и из контекстного меню выбрать пункт View Code. Содержимое модуля будет отображено в отдельном окне.

Создание переменной (поля) внутри класса аналогично созданию обычной глобальной переменной. Метод внутри класса создается так же, как и обычная подпрограмма. Доступ к полям и методам класса производится как к глобальным идентификаторам. Управлять доступом к идентификаторам внутри класса позволяют следующие спецификаторы:

Если спецификатор не указан, то:

При объявлении полей спецификаторы Public и Private указываются вместо ключевого слова Dim:

Private x_ As Integer

При объявлении методов спецификаторы Public, Private и Friend указываются перед ключевыми словами Sub и Function:

Public Function GetX() As Integer
   GetX = x_
End Function

Создание экземпляра класса

Для хранения ссылки на экземпляр класса предназначен тип Object. Вместо указания этого типа следует использовать названия конкретных классов. В этом случае компилятор сможет заранее получить информацию об объекте. Если указан тип Object, то информация будет доступна только в процессе выполнения программы. Чтобы присвоить значение переменной необходимо перед именем переменной дополнительно указать оператор Set.

Создать экземпляр пользовательского класса можно тремя способами:

' Способ 1
Dim C As Object
Set C = New MyClass
' Способ 2
Dim C As MyClass
Set C = New MyClass
' Способ 3
Dim C As New MyClass

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

При присваивании значения перед именем переменной указывается оператор Set. Если оператор не указать и при этом объявить переменную с типом Variant, то вместо ссылки на объект будет присвоено значение свойства по умолчанию. Если же указан конкретный класс, то компилятор выведет сообщение об ошибке. После оператора присваивания перед именем класса добавляется оператор New. Если оператор New указан при объявлении объектной переменной, то экземпляр класса создается неявным образом при первом обращении к объекту, поэтому сохранять ссылку с помощью оператора Set не нужно.

Переменной типа Object можно присвоить специальное значение Nothing, которое означает отсутствие ссылки на объект. С помощью этого значения производится удаление ссылки из переменной. Если на объект нет ссылок, то он автоматически удаляется. Пример:

Dim obj As Object
Set obj = Nothing

При обращении к открытым методам класса используется следующий формат:

<Экземпляр класса>.<Имя метода>([<Параметры>])

Обращение к открытым полям класса осуществляется аналогично:

<Экземпляр класса>.<Имя поля>

Внутри класса поле может содержать экземпляр другого класса. В этом случае, чтобы получить доступ к вложенным объектам, следует указать целую цепочку из объектов через оператор . (точка). Например, чтобы изменить значение ячейки A1, расположенной на листе Лист1 книги Книга1.xlsm, следует указать такую цепочку:

Application.Workbooks("Книга1.xlsm").Worksheets("Лист1") _
           .Range("A1").Value = "10"

Определим класс MyClass с закрытым полем x_ и открытыми методами SetX() и GetX(), позволяющими задать и получить значение поля соответственно, а затем создадим экземпляр класса из модуля Module1 и вызовем эти методы (листинг 10.1).

Листинг 10.1. Создание экземпляра класса

' Содержимое модуля класса MyClass
Private x_ As Integer

Public Sub SetX(n As Integer)
   x_ = n
End Sub

Public Function GetX() As Integer
   GetX = x_
End Function

...
' Содержимое модуля Module1
Sub Тест()       ' Запускаем эту процедуру
   Dim C As New MyClass
   C.SetX 10
   Debug.Print C.GetX()         ' 10
End Sub

Если поле x_ объявить открытым, то можно отказаться от использования методов SetX() и GetX() и напрямую изменять значение поля (листинг 10.2). Однако, такой подход нарушает один из принципов ООП — принцип инкапсуляции, сокрытия данных внутри класса.

Листинг 10.2. Доступ к открытому полю

' Содержимое модуля класса MyClass
Public x As Integer

...
' Содержимое модуля Module1
Sub Тест()       ' Запускаем эту процедуру
   Dim C As New MyClass
   C.x = 10
   Debug.Print C.x         ' 10
End Sub

Свойства класса

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

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

[Public | Private | Friend] [Static]
Property Let <Имя свойства>(<Параметр>)
   <Тело метода>
End Property
[Public | Private | Friend] [Static]
Property Set <Имя свойства>(<Параметр>)
   <Тело метода>
End Property
[Public | Private | Friend] [Static]
Property Get <Имя свойства>() [As <Тип>]
   <Тело метода>
   <Имя ствойства> = <Возвращаемое значение>
End Property

Для преждевременного завершения выполнения методов предназначена инструкция:

Exit Property

Присваивание значение свойству осуществляется с помощью оператора =. Например, если существует свойство X, то присвоить новое значение можно так:

Dim C As New MyClass
C.X = 10

Получить значение свойства X можно следующим образом:

Debug.Print C.X

Переделаем пример из листинга 10.1 и используем свойства классов вместо методов SetX() и GetX() (листинг 10.3).

Листинг 10.3. Свойства класса

' Содержимое модуля класса MyClass
Private x_ As Integer

Property Let X(n As Integer)
   x_ = n
End Property

Property Get X() As Integer
   X = x_
End Property

...
' Содержимое модуля Module1
Sub Тест()       ' Запускаем эту процедуру
   Dim C As New MyClass
   C.X = 10
   Debug.Print C.X         ' 10
End Sub

Конструктор и деструктор

В верхней части окна, содержащего модуль класса, расположены два раскрывающихся списка. В левом списке (называется Object) существует пункт Class. Если выбрать этот пункт, то в правом списке (называется Procedure) отобразятся названия двух событий:

Private Sub Class_Initialize()
...
End Sub

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

Private Sub Class_Terminate()
...
End Sub

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

В качестве примера продемонстрируем последовательность вызова конструктора и деструктора при создании и удалении объекта (листинг 10.4).

Листинг 10.4. Порядок вызова конструктора и деструктора

' Содержимое модуля класса MyClass
Public x As Integer

Private Sub Class_Initialize() ' Конструктор
   x = 0      ' Инициализация поля
   Debug.Print "Вызван конструктор"
End Sub

Private Sub Class_Terminate() ' Деструктор
   Debug.Print "Вызван деструктор"
End Sub

...
' Содержимое модуля Module1
Sub Тест()       ' Запускаем эту процедуру
   Dim C As New MyClass
   C.x = 10
   Debug.Print C.x         ' 10
   Set C = Nothing
End Sub

Результат выполнения:

Вызван конструктор
10
Вызван деструктор

Ключевое слово Me

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

Листинг 10.5. Ключевое слово Me

' Содержимое модуля класса MyClass
Private x_ As Integer

Private Sub Class_Initialize() ' Конструктор
   Module1.Init Me
End Sub

Property Let X(n As Integer)
   x_ = n
End Property

Property Get X() As Integer
   X = x_
End Property

...
' Содержимое модуля Module1
Sub Init(ByRef obj As MyClass)
   obj.X = 10
End Sub

Sub Тест()       ' Запускаем эту процедуру
   Dim C As MyClass
   Set C = New MyClass
   Debug.Print C.X      ' 10
End Sub
Предыдущая статья Все статьи Следующая статья