在 VBA 中,应该避免在类模块中修改文档的代码

2024-02-08

我开始使用 VBA 中的类,并欣赏 SO 上已经提供的一些精彩信息。

据我所知,似乎缺少的是对类中的代码应该做什么或我怀疑不应该做什么的解释。例如:

假设我有一个文档并希望插入/修改一个表。在这个例子中我想:

  • 检查表是否存在
  • if table does not exist:
    • 在特定位置添加表
    • 向表中添加信息(即添加行)
  • if table does exist
    • 在表中添加/删除信息
    • 根据某些标准对表进行排序

关于“排序”,我认为类模块非常适合根据某些标准确定信息放入表中的顺序。

但理想情况下:

  • 是否应该使用类模块(或第二类模块)来检查和编辑文档?

OR

  • 检查和/或编辑是否最好使用常规模块来完成?

还是没关系?如果有首选方法,那么每种方法的优点/缺点是什么?


首先,感谢您进入 OOP 的奇妙兔子洞!

简短回答:这取决于。


(非常)长的答案:

您需要避免从中提取[在编译时存在的]工作表Application.Worksheets (or Application.Sheets) 集合,并使用该工作表的CodeName反而。 VBA 创建一个全局范围的对象引用供您使用,以每个工作表的名称命名CodeName.

这就是这段代码的编译方式,Sheet1没有在任何地方声明:

Option Explicit

Sub Test()
    Debug.Print Sheet1.CodeName
End Sub

使用全局范围的“自由”对象变量,在工作表的代码隐藏之外的任何地方实现特定于工作表的功能的问题是,现在单独的模块是coupled接着就,随即Sheet1 object.

类模块取决于工作表。Any工作表。

你想要专注,cohesive模块-高内聚力. And 低耦合.

通过在另一个模块(无论是标准模块还是类模块)中编写特定于工作表的代码,您正在创建一个依赖性并不断增加coupling,这减少了可测试性- 考虑这段代码Class1:

Public Sub DoSomething()
    With Sheet1
        ' do stuff
    End With
End Sub

Now Class1只能与Sheet1。这样会更好:

Public Sub DoSomething(ByVal sheet As Worksheet)
    With sheet
        ' do stuff
    End With
End Sub

这里发生了什么?依赖注入。我们有一个依赖性在特定的工作表上,但我们不是针对该特定对象进行编码,而是告诉世界“给我任何工作表我会用它做我的事”。那是在method level.

如果一个类意味着使用单个特定工作表,并公开使用该工作表执行各种操作的多个方法,则具有ByVal sheet As Worksheet每个方法上的参数没有多大意义。

相反,您将其作为属性注入:

Private mSheet As Worksheet

Public Property Get Sheet() As Worksheet
    Set Sheet = mSheet
End Property

Public Property Set Sheet(ByVal value As Worksheet)
    Set mSheet = value
End Property

现在该类的所有方法都可以使用Sheet...唯一的问题是使用该类的客户端代码现在需要记住Set that Sheet属性,否则可能会出现错误。在我看来,这是糟糕的设计。

一种解决方案可能是推动依赖注入原理更进一步,实际上依赖于抽象;我们将要为该类公开的接口形式化,使用another将充当接口的类模块 - 即IClass1类没有实现任何东西,它只是定义了所公开内容的存根:

'@Interface
Option Explicit

Public Property Get Sheet() As Worksheet
End Property

Public Sub DoSomething()
End Sub

Our Class1类模块现在可以实施那个界面,如果你一直关注到这里,希望我不会在这里迷失你:

注意:模块和成员属性在 VBE 中不可见。他们与相应的代表在这里橡皮鸭 http://rubberduckvba.com注释。

'@PredeclaredId
'@Exposed
Option Explicit
Implements IClass1

Private mSheet As Worksheet

Public Function Create(ByVal pSheet As Worksheet) As IClass1
    With New Class1
        Set .Sheet = pSheet
        Set Create = .Self
    End With
End Function

Friend Property Get Self() As IClass1
    Set Self = Me
End Property

Private Property Get IClass1_Sheet() As Worksheet
    Set IClass1_Sheet = mSheet
End Property

Private Sub IClass1_DoSomething()
    'implementation goes here
End Sub

This Class1类模块呈现two接口:

  • Class1 members, accessible from the PredeclaredId instance:
    • Create(ByVal pSheet As Worksheet) As IClass1
    • Self() As IClass1
  • IClass1 members, accessible from the IClass1 interface:
    • Sheet() As Worksheet
    • DoSomething()

现在调用代码可以如下所示:

Dim foo As IClass1
Set foo = Class1.Create(Sheet1)
Debug.Assert foo.Sheet Is Sheet1
foo.DoSomething

因为它是针对IClass1接口,调用代码只能“看到”Sheet and DoSomething成员。因为VB_PredeclaredId的属性Class1, the Create可以通过以下方式访问该功能Class1 默认实例,几乎一模一样Sheet1无需创建实例即可访问(UserForm 类具有默认实例, too).

这是factory设计模式:我们正在使用默认实例 as a factory其作用是创建并初始化一个实现IClass1接口,其中Class1恰好正在实施。

With Class1完全解耦Sheet1,绝对没有什么问题Class1负责在其初始化的任何工作表上需要发生的所有事情。

Coupling受到照顾。Cohesion完全是另一个问题:如果你发现Class1长出毛发和触手,并负责很多事情,你甚至不知道它是为了什么而写的,很可能单一责任原则正在遭受打击,并且IClass1接口有很多不相关的成员,以至于接口隔离原则 is also受到打击,其原因可能是因为界面设计时没有考虑到开闭原理心里。


上述内容无法使用标准模块来实现。标准模块与 OOP 的配合不太好,这意味着更紧密的耦合,从而降低了可测试性。


TL;DR:

没有一种“正确”的设计方法anything.

  • 如果你的代码可以处理紧密耦合具有特定的工作表,更喜欢在该工作表的代码隐藏中实现该工作表的功能,以便更好cohesion。仍然使用专门的对象(类)来执行专门的任务:如果您的工作表代码隐藏负责设置数据库连接、通过网络发送参数化查询、检索结果并将它们转储到工作表中,那么您就是做错事™现在,在不访问数据库的情况下单独测试该代码是不可能的。
  • 如果您的代码更复杂并且无法与特定工作表紧密耦合,或者工作表在编译时不存在,在可以使用的类中实现功能any工作表,并有一个类负责model该运行时创建的工作表的。

IMO 标准模块只能用于公开入口点(宏、UDF、Rubberduck 测试方法以及Option Private Module,一些常见的实用函数),并且包含相当少的代码,仅初始化对象及其依赖项,然后一直都是课程.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 VBA 中,应该避免在类模块中修改文档的代码 的相关文章

  • 复制两个 Excel 实例之间的范围

    我正在运行两个单独的 Excel 实例 并且尝试将数据从一个工作簿中的范围复制到另一个工作簿中 我有这个代码 Sub CopyValues Dim xlApp As Excel Application Set xlApp GetObject
  • 出现错误时如何中断?

    我有一个函数 其中某个地方有一些错误导致它返回 VALUE当我尝试在Excel中执行它时 我不知道错误在哪里 单步调试代码也很乏味 所以我希望调试器在发生错误时立即中断 我尝试去Tools gt options gt General gt
  • Apache POI 编号列表

    我在用apache poi在 MS Word 文档中写入一些数据 我已经花了几个小时试图弄清楚如何创建编号列表但我还没有取得任何成果 我已经经历过this https stackoverflow com questions 31310020
  • Excel IE 自动化识别“css”元素

    仍然在抓取网站 从网页打印等方面进行掠夺 但遇到了一个障碍 我希望有人可以帮助我 下图显示了我想要激活的选项卡 我有代码可以到达那里 但无法激活该选项卡 虽然最后一行代码可以在我的台式电脑上运行 但我无法让它在我的其他电脑上运行 我可能可以
  • 如何从有条件的列中获取最新值

    我在 Excel 中有一个表 其中包含以下列 Date 人名 金额 英镑 该表用于记录人们何时付钱给我 通常 我可以让不止一个人在同一天向我付款 而且 随着时间的推移 同一个人会在很多天向我付款 记录添加到表格底部 以便按日期排序 但不再按
  • 我怎样才能优化这个vba循环代码?

    嗨 我写了这段代码 但这段代码非常慢 我该如何优化这段代码 Private Sub printItem r lastCol objStream FirstCol 1 Dim strFirst As String strFirst CStr
  • Excel 中数字的条件格式(自定义格式)具有多个条件

    我想根据单元格中的值动态地将数字格式化为 M 百万 B 十亿 或 K 千 我尝试过 但这不起作用 gt 1000000000 0 B gt 1000000 0 M gt 1000 0 K 0 如果我给出任意两个条件 它就会起作用 例如 gt
  • 基于两列值的VLOOKUP

    我有一个表 其中一列包含用户 ID 这些被多次输入以显示特定主题的结果 ID Topic Pass Fail 71086686 Science P 71086686 Maths P 71086686 Tech P 71086686 ICT
  • 大型数据集的报告工具/查看器

    我有一个数据处理系统 可以根据其处理的数据生成非常大的报告 我所说的 大 是指该系统的 小 执行在转储到 CSV 文件中时会产生大约 30 MB 的报告数据 而大数据集大约为 130 150 MB 我确信有人有更大的想法 大 但这不是重点
  • 如何使用 C# 和 OleDbConnection 读取 .xlsx 和 .xls 文件?

    以前我使用的是ExcelPackage从 xlsx 文件中读取数据 效果很好 但后来我意识到ExcelPackage不适用于旧的 xls 格式 所以我升级到使用OleDbConnection代替ExcelPackage像这样 var fil
  • Excel 仅粘贴特殊使用值,还将值的数据类型与值一起复制到目标单元格中

    我一直在尝试更多地了解 Excel 单元格 特别是它们的数据类型 如果有人对细节感兴趣 我的调查位于下面的编号点中 我的结论被标记为 A 到 D 我真的很感兴趣是否有人有什么可以补充的 答 每个 Excel 单元格都有一个属性 用于定义它将
  • 如何在没有任何数据行的情况下读取 Excel 表/ListObject 中的计算列的公式

    我有一个以外部查询作为数据源的 ListObject 它返回 18 列 ListObject 之前已添加了额外的 4 个计算列 现在 ListObject 有 0 个数据行 但是 虽然有 0 个数据行 但我似乎无法读取计算列的预定义公式 如
  • 是否可以编写自定义 Power Query 连接器?

    在 来自其他来源 下的 Power Query 中 我们看到许多专业提供商 Facebook SAP SalesForce 等 我有兴趣编写一个自定义提供程序来访问无法通过任何内置连接器使用的本地专有数据存储 我知道 访问没有支持连接器的数
  • 在 VBA 中调用批处理文件无法正常工作

    我正在尝试创建一个可供其他人使用的程序 目前 我的文件位于目录中C Documents and Settings jpmccros Desktop test 该目录包含我的macro xlsm names bat 还有另一个子目录名为Dat
  • 错误:如何读取 Excel 中的空单元格

    我正在尝试使用 POI 从 Excel 读取数据 如何检查该单元格是否为空单元格 我不知道缺少什么我认为这应该有效 java util Iterator
  • 如何检查单元格是否包含通配符星号 (*) 字符

    考虑以下两个公式 IF SEARCH A1 true false IF SEARCH CHAR 42 A1 true false 我正在用它来尝试检测单元格是否包含 字符 但这对所有单元格返回 true 我只能假设 Excel 看到 也许作
  • 使用字符串数组作为子字符串参数的 VBA InStr 函数 (Excel)

    找了很久了 第一次提问 目标 循环遍历包含地址的列 根据单元格包含的邮政编码为单元格偏移 0 6 分配一个值 城市名称 这是我到目前为止所得到的 缩短的数组长度 Sub LabelCell Dim SrchRng As Range cel
  • VBA:如何从 MS Access 运行另一个应用程序

    我一直在试图解决这个问题 但似乎我在任何地方都找不到解决该问题的方法 这是第一部分 VBA Shell 命令始终返回 找不到文件 https stackoverflow com questions 10439024 vba shell co
  • VBA半正矢公式

    我正在尝试将半正矢公式应用到Excel函数中 它看起来像这样 Public Function Haversine Lat1 As Variant Lon1 As Variant Lat2 As Variant Lon2 As Variant
  • 自动创建 Outlook 约会

    我有一个跟踪到期日期的电子表格 excel 2003 我想知道是否有办法让这些到期日期在 Outlook 中创建约会 提醒 到期日期位于电子表格的一个字段中 实体名称位于另一列中 理想情况下 我希望 Outlook 2003 能够获取日期

随机推荐