在VBA中使用类的好处是什么?

31

我正在Excel中进行一些VBA编程,有一个工作簿,需要将其中所有数据表复制到另一个表中。新表将有几个标题行,我想跟踪它们的位置,以便不必经常查找它们中的单词。

最简单的方法是使用类,并在Excel工作簿打开时让它们保持运行状态吗?还是这会使它变得沉重和难以处理,我应该继续使用子程序?使用类的好处是什么?我并不像拥有几个对象,只有工作表和列上的验证。

3个回答

75
使用类而不仅仅是子程序的优点在于,类创建了一种抽象层次,使您能够编写更清晰的代码。诚然,如果您以前从未在VBA中使用过类,则需要学习曲线,但我认为它肯定值得花时间去弄清楚。
一个关键指示,表明您应该转换到类,是如果您不断地向函数和子例程添加参数。在这种情况下,最好几乎总是使用类。
我从我的以前的Stack Overflow答案之一复制了有关类的解释:

以下是一个漫长的示例,展示了如何使用类可以帮助您。虽然这个示例很长,但它将向您展示一些面向对象编程的原则如何真正帮助您整理代码。

在VBA编辑器中,转到插入>类模块。在属性窗口(默认情况下位于屏幕左下角),将模块的名称更改为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,那么查看这本书是非常值得的。

感谢提供示例代码。在函数TestGetHoursWorked中,声明Dim arrWli() As WorkLogItem是否必要?类让我感到困惑,就我所知,即使没有这行代码,示例也可以运行。这是某种类魔法所需吗? - SlowLearner
@SlowLearner:不是,看起来是未使用的代码。 - user7857211
@Ben McCormack感谢您对使用类的详细而好的解释。这使我有机会问一些我一直想知道的事情。在您的“PopulateArray”函数中,为什么您(和其他人)要创建一个集合,添加到其中,然后将函数设置为等于该集合?为什么不直接将函数设置为一个新的集合并直接添加到其中呢? - Brian

5
如果子程序很多或子程序非常长,则将代码结构化为类可能有助于解决问题。如果只有几个子程序,每个子程序只有10行代码,则这种做法是过度的。将代码结构化为类的好处在于,在以后需要阅读和更改代码时更易于理解。因此,将代码结构化为类的另一个原因是,如果代码在以后可能需要更改,则更易维护。

4
有一个优势是其他贡献者可能没有提到的(如果我错过了Ben McCormack的出色回答中的话就抱歉),如果你的VBA脚本可能会被重新编程,那么类可以发挥它们的作用。
例如,我正在设计一种订单管理系统。它将由几位同事使用相当长一段时间,但如果订购规则发生变化,则可能需要重新编程。因此,我设计了一个基本的库存项目类,它收集有关库存项目的所有信息。有关如何分析任何订单的数据的规则是通过易于访问和良好注释的子例程编写的。通过这样做,我希望未来的VBA程序员可以轻松更改生成订单的数学规则,而无需处理有关特定库存项目的所有数据聚合的方式(这是在类内部的子例程和函数中完成的,这些函数和函数在类被传递库存号时被激活)。类的公共属性也可以被智能感知所识别,使下一个程序员以及你自己更容易地使用它。
我想重点是,如果类编码了一些基本信息或某些概念对象,这些信息或对象始终与程序使用的上下文相关,那么类可以以这种方式为后续用户简化工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接