繁体   English   中英

何时在 VBA 中使用类?

[英]When to use a Class in VBA?

什么时候适合在 Visual Basic for Applications (VBA) 中使用类?

我假设加速开发和减少引入错误对于大多数支持 OOP 的语言来说是一个共同的好处。 但是对于 VBA,是否有特定的标准?

这取决于谁将开发和维护代码。 典型的“高级用户”宏编写者破解小型临时应用程序可能会因使用类而感到困惑。 但是对于认真的开发,使用类的原因与其他语言相同。 您有与 VB6 相同的限制 - 没有继承 - 但您可以通过使用接口来实现多态性。

类的一个很好的用途是表示实体和实体的集合。 例如,我经常看到 VBA 代码将 Excel 范围复制到二维数组中,然后使用如下代码操作二维数组:

Total = 0
For i = 0 To NumRows-1
    Total = Total + (OrderArray(i,1) * OrderArray(i,3))
Next i

将范围复制到具有适当命名属性的对象集合中更具可读性,例如:

Total = 0
For Each objOrder in colOrders
    Total = Total + objOrder.Quantity * objOrder.Price
Next i

另一个例子是使用类来实现 RAII 设计模式(google for it)。 例如,我可能需要做的一件事是取消保护工作表,进行一些操作,然后再次保护它。 使用类可确保即使代码中出现错误,工作表也将始终受到保护:

--- WorksheetProtector class module ---

Private m_objWorksheet As Worksheet
Private m_sPassword As String

Public Sub Unprotect(Worksheet As Worksheet, Password As String)
    ' Nothing to do if we didn't define a password for the worksheet
    If Len(Password) = 0 Then Exit Sub

    ' If the worksheet is already unprotected, nothing to do
    If Not Worksheet.ProtectContents Then Exit Sub

    ' Unprotect the worksheet
    Worksheet.Unprotect Password

    ' Remember the worksheet and password so we can protect again
    Set m_objWorksheet = Worksheet
    m_sPassword = Password
End Sub

Public Sub Protect()
    ' Protects the worksheet with the same password used to unprotect it
    If m_objWorksheet Is Nothing Then Exit Sub
    If Len(m_sPassword) = 0 Then Exit Sub

    ' If the worksheet is already protected, nothing to do
    If m_objWorksheet.ProtectContents Then Exit Sub

    m_objWorksheet.Protect m_sPassword
    Set m_objWorksheet = Nothing
    m_sPassword = ""
End Sub

Private Sub Class_Terminate()
    ' Reprotect the worksheet when this object goes out of scope
    On Error Resume Next
    Protect
End Sub

然后,您可以使用它来简化代码:

Public Sub DoSomething()
   Dim objWorksheetProtector as WorksheetProtector
   Set objWorksheetProtector = New WorksheetProtector
   objWorksheetProtector.Unprotect myWorksheet, myPassword

   ... manipulate myWorksheet - may raise an error

End Sub 

当这个 Sub 退出时, objWorksheetProtector 超出范围,并且工作表再次受到保护。

我认为标准与其他语言相同

如果您需要将几条数据和一些方法联系在一起,并且还需要专门处理创建/终止对象时发生的情况,那么类是理想的

假设您有几个程序在您打开表单时触发并且其中一个需要很长时间,您可能会决定要对每个阶段进行计时......

您可以创建一个秒表类,其中包含用于启动和停止的明显函数的方法,然后您可以添加一个函数来检索到目前为止的时间并在文本文件中报告它,使用表示正在计时的进程名称的参数。 您可以编写逻辑来仅记录最慢的性能以进行调查。

然后,您可以添加一个进度条对象,其中包含打开和关闭它的方法,并显示当前操作的名称,以及以毫秒为单位的时间以及基于先前存储的报告等可能剩余的时间

另一个示例可能是,如果您不喜欢 Access 的用户组垃圾,您可以创建自己的 User 类,其中包含登录和注销方法以及组级用户访问控制/审核/记录某些操作/跟踪错误等功能

当然,您可以使用一组不相关的方法和大量变量传递来做到这一点,但是将它们全部封装在一个类中对我来说似乎更好。

您迟早会接近 VBA 的极限,但它是一种非常强大的语言,如果您的公司将您与它联系起来,您实际上可以从中获得一些好的、复杂的解决方案。

类在处理更复杂的 API 函数时非常有用,尤其是当它们需要数据结构时。

例如,GetOpenFileName() 和 GetSaveFileName() 函数采用具有许多成员的 OPENFILENAME 结构。 您可能不需要利用所有这些,但它们就在那里并且应该被初始化。

我喜欢将结构 (UDT) 和 API 函数声明包装到 CfileDialog 类中。 Class_Initialize 事件设置结构成员的默认值,这样当我使用类时,我只需要设置我想要更改的成员(通过 Property 程序)。 标志常量被实现为枚举。 因此,例如,要选择要打开的电子表格,我的代码可能如下所示:

Dim strFileName As String
Dim dlgXLS As New CFileDialog

With dlgXLS
  .Title = "Choose a Spreadsheet"
  .Filter = "Excel (*.xls)|*.xls|All Files (*.*)|*.*"
  .Flags = ofnFileMustExist OR ofnExplorer

  If OpenFileDialog() Then
    strFileName = .FileName
  End If
End With
Set dlgXLS = Nothing

该类将默认目录设置为“我的文档”,但如果我愿意,可以使用 InitDir 属性更改它。

这只是一个类如何在 VBA 应用程序中发挥巨大作用的一个例子。

我不会说有一个特定的标准,但我从来没有真正找到一个有用的地方在 VBA 代码中使用类。 在我看来,它与 Office 应用程序周围的现有模型是如此相关,以至于在该对象模型之外添加额外的抽象只会使事情变得混乱。

这并不是说无法在 VBA 中为类找到有用的位置,或者无法使用类做非常有用的事情,只是我从未发现它们在那种环境中有用。

如果我想创建一个自我封装的代码包,我将使用类,我将在为各种客户遇到的许多 VBA 项目中使用该代码包。

对于数据递归(又名 BOM 处理),自定义类非常有用,我认为有时是必不可少的。 你可以在没有类模块的情况下制作递归函数,但是很多数据问题无法有效解决。

(我不知道为什么人们不为 VBA 兜售 BOM 库集。也许 XML 工具有所作为。)

多个表单实例是一个类的常见应用(许多自动化问题否则无法解决),我认为问题是关于自定义类。

当我需要做某事时,我会使用类,而类会做得最好:) 例如,如果您需要响应(或拦截)事件,那么您需要一个类。 有些人讨厌 UDT(用户定义类型),但我喜欢它们,所以如果我想要纯英语的自文档代码,我会使用它们。 Pharmacy.NCPDP 比 strPhrmNum 更容易阅读 :) 但是 UDT 是有限的,所以说我希望能够设置 Pharmacy.NCPDP 并填充所有其他属性。 而且我也想做到这一点,这样你就不会意外地改变数据。 然后我需要一个类,因为您在 UDT 中没有只读属性等。

另一个考虑因素只是简单的可读性。 如果您正在处理复杂的数据结构,知道您只需要调用 Company.Owner.Phone.AreaCode 然后尝试跟踪所有内容的结构通常是有益的。 特别是对于那些在你离开 2 年后必须维护该代码库的人:)

我自己的两分钱是“有目的的代码”。 不要无缘无故地使用课程。 但如果你有理由,那就去做吧:)

您还可以在不使用实际类的情况下重用 VBA 代码。 例如,如果您有一个称为 VBACode。 您可以使用以下语法访问任何模块中的任何函数或子:

VBCode.mysub(param1, param2)

如果您创建对模板/文档的引用(就像您创建 dll 一样),您可以以相同的方式引用其他项目的代码。

使用面向对象编程开发软件,即使使用 Microsoft Access,通常也是一种很好的做法。 它将通过允许对象松散耦合以及许多优势来实现未来的可伸缩性。 这基本上意味着您系统中的对象将更少相互依赖,因此重构变得容易得多。 您可以使用类模块来实现这一点。 缺点是您无法在 VBA 中执行类继承或多态性。 最后,关于使用类没有硬性规定,只有最佳实践。 但请记住,随着您的应用程序的增长,使用类进行维护变得更加容易。

由于在 VBA 中使用类有很多代码开销,我认为一个类必须提供比其他语言更多的好处:

所以这是在使用类而不是函数之前要考虑的事情:

  • vba 中没有类继承。 所以当你在不同的类中做类似的小事情时,准备复制一些代码。 当您想要使用接口并想要在不同的类中实现一个接口时,尤其会发生这种情况。

  • vba 类中没有内置的构造函数。 就我而言,我创建了一个如下所示的额外函数来模拟这一点。 但是诅咒,这也是开销,并且可以被如何使用该类的人忽略。 另外:由于不可能使用具有相同名称但参数不同的不同函数,因此您必须为“构造函数”函数使用不同的名称。 此外,这些功能会导致额外的调试步骤,这可能会很烦人。

Public Function MyClass(ByVal someInit As Boolean) As MyClassClass Set MyClass = New MyClassClass Call MyClass.Init(someInit) End Function
  • 开发环境不为类名提供“goto 定义”。 这可能很烦人,尤其是在使用带有接口的类时,因为您总是必须使用模块浏览器来跳转到类代码。

  • 对象变量在不同地方的使用与其他变量类型不同。 所以你必须使用额外的“Set”来分配一个对象

Set varName = new ClassName

  • 如果您想对对象使用属性,则由不同的设置器完成。 你必须使用“set”而不是“let”

  • 如果您在 vba 中实现接口,则函数名称被命名为“InterfaceName_functionName”并定义为私有。 因此,只有将变量强制转换为接口时,才能使用接口函数。 如果你想使用原始类的函数,你必须创建一个额外的“公共”函数,它只调用接口函数(见下文)。 这也创建了一个额外的调试步骤。

'content of class-module: MyClass implements IMyInterface private sub IMyInterface_SomeFunction() 'This can only be called if you got an object of type "IMyInterface" end function private sub IMyInterface_SomeFunction() 'You need this to call the function when having an object of the type "MyClass" Call IMyInterface_SomeFunction() end function

这表示:

  • 我不! 当它们不包含成员变量时使用类。
  • 我知道开销并且不使用类作为默认值来做事。 通常只使用函数是在 VBA 中做事的默认方式。

我创建的类示例,我发现它们很有用:

  • 集合类:例如缺少提供集合功能的 StringCollection、LongCollection vba
  • DbInserter-Class:创建插入语句的类

我创建的但我发现没有用的类的示例:

  • Converter-class:提供将变量转换为其他类型的功能的类(例如 StringToLong、VariantToString)
  • StringTool-class:一个可以为字符串提供一些功能的类。 例如 StartsWith

您可以在访问中定义一个比记录集和查询定义更方便的 sql 包装类。 例如,如果要根据另一个相关表中的条件更新表,则不能使用联接。 您可以构建一个 vba recorset 和 querydef 来做到这一点,但是我发现使用类更容易。 此外,您的应用程序可能有一些需要超过 2 个表的概念,为此使用类可能会更好。 例如,您的应用程序跟踪事件。 事件有几个属性,将保存在几个表中{用户及其联系人或个人资料、事件描述; 状态跟踪; 帮助支持人员回复事件的清单; 回复 ...} 。 要跟踪所有涉及的查询和关系,oop 可能会有所帮助。 能够做 Incident.Update(xxx) 而不是所有的编码是一种解脱......

在 VBA 中,我更喜欢类而不是模块:

  • (常见情况)我想要一个公共结构(类)的多个同时实例(对象),每个实例(对象)都有自己的独立属性。
    例子:
    将 EdgeTabGoogle 调暗为新的 Selenium.EdgeDriver
    将 EdgeTabBing 调暗为新的 Selenium.EdgeDriver
    '打开两者,然后做一些事情并从两者中读取数据,然后关闭两者
  • (有时)我想利用 Class_Initialize 和 Class_Terminate 自动功能
  • (有时)我想要程序的层次树(对于变量来说,“类型”链就足够了),以获得更好的可读性和智能感知
  • (很少)我希望公共变量或过程不全局显示在 Intellisense 中(除非前面有对象名称)

我不明白为什么 VBA 的标准与另一种语言有任何不同,特别是如果您指的是 VB.NET。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM