我开始在VBA中使用Classes,并欣赏SO上已有的一些精彩信息。
据我所知,似乎缺乏的是解释一个类中的代码应该或者我怀疑不应该做什么。例如:
假设我有一份文件并希望插入/修改表格。在这个例子中,我想:
关于'排序',我认为类模块非常适合根据某些标准确定信息应该放入表中的顺序。
但最理想的是:
OR
或者不重要吗?如果有一种首选方式,那么每种方法的优点/缺点是什么?
[编辑 - 此处的后续问题:Understanding classes in VBA - help improve these comments ]
答案 0 :(得分:2)
首先,为进入OOP精彩的兔子洞而感到荣幸!
简短回答:取决于。
(非常)长的答案:
您希望避免从Application.Worksheets
(或Application.Sheets
)集合中提取[在编译时存在]的工作表,并使用该表格{{1相反。 VBA创建了一个全局范围的对象引用供您使用,以每个工作表CodeName
命名。
这个代码如何编译,CodeName
没有在任何地方声明:
Sheet1
使用该全局范围" free"在工作表的代码隐藏之外的任何地方实现工作表特定功能的问题。对象变量,就是单独的模块现在与该Option Explicit
Sub Test()
Debug.Print Sheet1.CodeName
End Sub
对象耦合。
您需要专注的内聚模块 - 高凝聚力。 低耦合。
通过在另一个模块(无论是标准模块还是类模块)中编写特定于工作表的代码,您可以创建依赖并增加耦合,降低可测试性 - 在Sheet1
:
Class1
现在Public Sub DoSomething()
With Sheet1
' do stuff
End With
End Sub
只能与Class1
一起使用。这会更好:
Sheet1
这里发生了什么? 依赖注入。我们在特定工作表上有一个依赖,但我们不是针对该特定对象进行编码,而是告诉全世界"给我任何工作表和我用它做我的事情"。那是方法级别。
如果一个类意味着使用单个特定工作表,并且公开了多个使用该工作表执行各种操作的方法,那么在每个方法上都有Public Sub DoSomething(ByVal sheet As Worksheet)
With sheet
' do stuff
End With
End Sub
参数并没有多大意义。
相反,您将其作为财产注入:
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
属性,否则可能会出现错误。那个设计糟糕的IMO。
一种解决方案可能是将依赖注入原则进一步推进,实际上依赖于抽象;我们正式化了我们想要为该类公开的接口,使用另一个类模块作为接口 - Sheet
类没有实现任何东西,它只定义了存根揭露了什么:
IClass1
我们的'@Interface
Option Explicit
Public Property Get Sheet() As Worksheet
End Property
Public Sub DoSomething()
End Sub
课程模块现在可以实施该界面,如果你一直在关注这一点,希望我不会在这里失去你:
注意:VBE中不显示模块和成员属性。他们在此处使用相应的Rubberduck注释代表。
Class1
这个'@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
类模块提供了两个接口:
Class1
个成员,可从Class1
个实例访问:
PredeclaredId
Create(ByVal pSheet As Worksheet) As IClass1
Self() As IClass1
个成员,可从IClass1
界面访问:
IClass1
Sheet() As Worksheet
现在调用代码看起来像这样:
DoSomething()
因为它是针对Dim foo As IClass1
Set foo = Class1.Create(Sheet1)
Debug.Assert foo.Sheet Is Sheet1
foo.DoSomething
接口编写的,所以仅调用代码"看到" IClass1
和Sheet
成员。由于DoSomething
的{{1}}属性,VB_PredeclaredId
函数可以通过Class1
默认实例访问,几乎与{{1}完全相同无需创建实例即可访问(UserForm类也具有默认实例)。
这是 factory 设计模式:我们将默认实例用作工厂,其作用是创建和初始化Create
接口的实现,Class1
恰好正在实现。
Sheet1
完全与IClass1
分离后,让Class1
对所有需要在其初始化的工作表上发生的事情负责,这绝对没有错。
耦合得到了解决。 Cohesion 完全是另一个问题:如果你发现Class1
正在增长头发和触角,并为许多事情负责,你甚至不知道它是什么写的,很可能是单一责任原则正在受到打击,而且Sheet1
界面有如此多的无关成员,接口隔离原则 也是> em>受到殴打,其原因可能是因为界面没有考虑到开放/封闭原则而设计。
上述内容无法通过标准模块实施。标准模块在OOP上不能很好地发挥作用,这意味着更紧密的耦合,从而降低了可测试性。
没有单一"对"设计任何东西的方法。
IMO标准模块应仅用于公开入口点(宏,UDF,Rubberduck测试方法,以及Class1
,一些常用的实用程序函数),并且包含的只是初始化对象及其依赖项的相当少的代码,然后它的类一直向下。