Я занимаюсь программированием на VBA в Excel 2007 и у меня есть одна рабочая книга, из которой все таблицы данных должны быть скопированы на другой лист. На новом листе будет несколько строк заголовков, и я хотел бы отслеживать, где они находятся, поэтому мне не нужно постоянно находить в них слова.
Это самая простая вещь - использовать классы и поддерживать их работоспособными, пока открыт документ Excel? Или это сделает его тяжелым и трудным в обращении, и я должен продолжать работать с подпрограммами? Каковы преимущества использования классов? Это не так, как у меня есть несколько объектов, только листы и проверки по столбцам.
Преимущество использования классов вместо подпрограмм заключается в том, что классы создают уровень абстракции, который позволяет писать более чистый код. По общему признанию, если вы никогда не использовали классы раньше в VBA, есть кривая обучения, но я считаю, что это, безусловно, стоит времени, чтобы понять это.
Одним из ключевых признаков, которые вы должны переключить на классы, является постоянное добавление параметров к вашим функциям и подпрограммам. В этом случае почти всегда лучше использовать классы.
Я скопировал объяснение классов из одного из моих предыдущих ответов на переполнение стека:
Вот длинный пример того, как использование класса может вам помочь. Хотя этот пример длинный, он покажет вам, как несколько принципов объектно-ориентированного программирования могут действительно помочь вам очистить ваш код.
В редакторе VBA перейдите к Insert > Class Module
. В окне "Свойства" (внизу слева от экрана по умолчанию) измените имя модуля на WorkLogItem
. Добавьте в класс следующий код:
Option Explicit
Private pTaskID As Long
Private pPersonName As String
Private pHoursWorked As Double
Public Property Get TaskID() As Long
TaskID = pTaskID
End Property
Public Property Let TaskID(lTaskID As Long)
pTaskID = lTaskID
End Property
Public Property Get PersonName() As String
PersonName = pPersonName
End Property
Public Property Let PersonName(lPersonName As String)
pPersonName = lPersonName
End Property
Public Property Get HoursWorked() As Double
HoursWorked = pHoursWorked
End Property
Public Property Let HoursWorked(lHoursWorked As Double)
pHoursWorked = lHoursWorked
End Property
Вышеприведенный код даст нам строго типизированный объект, специфичный для данных, с которыми мы работаем. Когда вы используете многомерные массивы для хранения ваших данных, ваш код напоминает это: arr(1,1)
- это идентификатор, arr(1,2)
- имя PersonName, а arr(1,3)
- HoursWorked. Используя этот синтаксис, трудно понять, что к чему. Предположим, вы по-прежнему загружаете свои объекты в массив, но вместо этого используете WorkLogItem
, который мы создали выше. Это имя, вы можете сделать arr(1).PersonName
, чтобы получить имя человека. Это значительно упрощает чтение кода.
Продолжим двигаться с этим примером. Вместо хранения объектов в массиве мы попытаемся использовать collection
.
Затем добавьте новый модуль класса и назовите его ProcessWorkLog
. Вставьте здесь следующий код:
Option Explicit
Private pWorkLogItems As Collection
Public Property Get WorkLogItems() As Collection
Set WorkLogItems = pWorkLogItems
End Property
Public Property Set WorkLogItems(lWorkLogItem As Collection)
Set pWorkLogItems = lWorkLogItem
End Property
Function GetHoursWorked(strPersonName As String) As Double
On Error GoTo Handle_Errors
Dim wli As WorkLogItem
Dim doubleTotal As Double
doubleTotal = 0
For Each wli In WorkLogItems
If strPersonName = wli.PersonName Then
doubleTotal = doubleTotal + wli.HoursWorked
End If
Next wli
Exit_Here:
GetHoursWorked = doubleTotal
Exit Function
Handle_Errors:
'You will probably want to catch the error that will '
'occur if WorkLogItems has not been set '
Resume Exit_Here
End Function
Вышеприведенный класс будет использоваться для "сделать что-то" с коллекцией WorkLogItem
. Первоначально мы просто подсчитываем общее количество отработанных часов. Позвольте проверить код, который мы написали. Создайте новый модуль (а не модуль класса на этот раз, просто "обычный" модуль). Вставьте следующий код в модуль:
Option Explicit
Function PopulateArray() As Collection
Dim clnWlis As Collection
Dim wli As WorkLogItem
'Put some data in the collection'
Set clnWlis = New Collection
Set wli = New WorkLogItem
wli.TaskID = 1
wli.PersonName = "Fred"
wli.HoursWorked = 4.5
clnWlis.Add wli
Set wli = New WorkLogItem
wli.TaskID = 2
wli.PersonName = "Sally"
wli.HoursWorked = 3
clnWlis.Add wli
Set wli = New WorkLogItem
wli.TaskID = 3
wli.PersonName = "Fred"
wli.HoursWorked = 2.5
clnWlis.Add wli
Set PopulateArray = clnWlis
End Function
Sub TestGetHoursWorked()
Dim pwl As ProcessWorkLog
Dim arrWli() As WorkLogItem
Set pwl = New ProcessWorkLog
Set pwl.WorkLogItems = PopulateArray()
Debug.Print pwl.GetHoursWorked("Fred")
End Sub
В приведенном выше коде PopulateArray()
просто создается коллекция WorkLogItem
. В вашем реальном коде вы можете создать класс для анализа ваших листов Excel или ваших объектов данных для заполнения коллекции или массива.
Код TestGetHoursWorked()
просто демонстрирует, как использовались классы. Вы заметили, что ProcessWorkLog
создается как объект. После его создания коллекция WorkLogItem
становится частью объекта pwl
. Вы заметили это в строке Set pwl.WorkLogItems = PopulateArray()
. Затем мы просто вызываем написанную нами функцию, которая действует на коллекцию WorkLogItems
.
Почему это полезно?
Предположим, что ваши данные изменяются, и вы хотите добавить новый метод. Предположим, что ваш WorkLogItem
теперь содержит поле для HoursOnBreak
, и вы хотите добавить новый метод для его вычисления.
Все, что вам нужно сделать, это добавить свойство к WorkLogItem
так:
Private pHoursOnBreak As Double
Public Property Get HoursOnBreak() As Double
HoursOnBreak = pHoursOnBreak
End Property
Public Property Let HoursOnBreak(lHoursOnBreak As Double)
pHoursOnBreak = lHoursOnBreak
End Property
Конечно, вам нужно будет изменить свой метод для заполнения вашей коллекции (метод образца, который я использовал, был PopulateArray()
, но для этого, вероятно, должен быть отдельный класс). Затем вы просто добавляете свой новый метод в класс ProcessWorkLog
:
Function GetHoursOnBreak(strPersonName As String) As Double
'Code to get hours on break
End Function
Теперь, если мы хотим обновить наш метод TestGetHoursWorked()
, чтобы вернуть результат GetHoursOnBreak
, все, что нам нужно было бы сделать, добавив следующую строку:
Debug.Print pwl.GetHoursOnBreak("Fred")
Если вы передали массив значений, представляющих ваши данные, вам нужно было бы найти все места в вашем коде, где вы использовали массивы, а затем обновить их соответствующим образом. Если вместо этого вы используете классы (и их созданные объекты), вы можете гораздо легче обновить свой код для работы с изменениями. Кроме того, когда вы разрешаете использовать класс несколькими способами (возможно, для одной функции требуется только 4 свойства объектов, а другая - 6), они все равно могут ссылаться на один и тот же объект. Это не позволяет вам иметь несколько массивов для разных типов функций.
Для дальнейшего чтения я настоятельно рекомендую получить копию VBA Developer Handbook, 2-е издание. Книга полна отличных примеров и передовых методов и тонны кода образца. Если вы инвестируете много времени в VBA для серьезного проекта, стоит потратить время на изучение этой книги.
Если есть много подпрограмм или подпрограмм, очень долго, то может помочь структурирование кода в классах. Если есть только пара подпрограмм, скажем, каждый из них составляет всего 10 строк кода, то это больше, чем убийство. Преимущество структурирования кода в классах заключается в том, что его легче читать и изменять, когда вы возвращаетесь к нему по линии. Таким образом, еще одна причина для структурирования кода в классах - это если код, вероятно, понадобится изменить строку
Есть еще одна вещь, которую вы могли бы добавить к преимуществам, заявленным другими участниками (извините, если это где-то в Ben McCormack отличный ответ, и я пропустил это). Классы могут использовать их, если ваш VBA script, вероятно, будет запрограммирован в какой-то момент.
Например, я разрабатываю систему управления заказами. Он должен использоваться несколькими коллегами довольно долгое время, но может потребоваться повторная прогамминга, если правила упорядочения изменится. Поэтому я разработал класс основных предметов, который собирает всю информацию об элементе запаса. Однако правила о том, как эти данные анализируются для любого заказа, записываются в легкодоступные и хорошо прокомментированные подпрограммы. Делая это, я надеюсь, что будущие программисты VBA могут легко изменить математические правила, с помощью которых создаются заказы, без необходимости иметь дело с тем, как все данные собираются по определенному элементу запаса (все это делается подпрограммами и функциями внутри класса, которые активируются, когда классу передается номер запаса). Общественные свойства класса также подхватываются intellisense, позволяя следующему программисту, а также самому себе, облегчить его время.
Думаю, дело в том, что классы могут облегчить жизнь для более поздних пользователей таким образом, если они кодируют некоторый базовый набор информации или какой-то концептуальный объект, который всегда может иметь отношение к контексту использования программы.