使用类而不是仅使用子例程的优点是,类创建了一个抽象级别,使您可以编写更简洁的代码。诚然,如果您以前从未在VBA中使用过类,那么这里会有一条学习曲线,但是我相信值得花时间弄清楚它。
如果要不断向函数和子例程中添加参数,则应切换到类的一个关键指示是。在这种情况下,几乎总是最好使用类。
我从one of my previous Stack Overflow answers复制了对类的解释:
这是一个使用类(class)如何帮助您的长例子。尽管此示例很冗长,但是它将向您展示一些面向对象编程的原理如何真正帮助您清理代码。
在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)
是ID,
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's Handbook, 2nd edition的副本。本书中包含许多出色的示例和最佳实践以及大量示例代码。如果您在一个严肃的项目上花大量时间在VBA上,那么值得您花时间阅读本书。