您正在抽象Worksheet
“代理”类的背后;根据定义,它与工作表耦合,您想要确保抽象是密封的,以免您看到泄漏的抽象并最终将其他代码与该Excel.Worksheet
类型耦合,从而破坏了整个目的。
对于项目的其余部分,工作表代理类充当了一个门面,它操纵和理解有关特定的所有信息Excel.Worksheet
:这样做的结果是您现在可以使用两个模块来抽象工作表的东西 - 工作表本身,和代理类:
- 工作表代码隐藏可以抽象
ListObject
/tables、命名范围等内容;使用Property Get
代理可以使用的成员。
- 工作表代理类从其余代码中抽象出工作表操作。
事实上,这种方法并没有为实际工作表代码隐藏留下太多空间/需要:我会开始在代理类中编写所有内容,如果该模块变得过于冗长,或者我发现它的抽象级别需要得到一个再高一点,然后我会将较低级别的东西移到工作表本身的代码隐藏中。
Workheet
模块和其他文档模块不应该实现接口 - 让工作表实现接口是混淆和崩溃 VBA 的好方法:不要这样做。所以这可能是你的代码隐藏:
Option Explicit
Public Property Get SomeSpecificRange() As Range
Set SomeSpecificRange = Me.Names("SomeSpecificRange").RefersToRange
End Property
然后代理类可以这样做:
Option Explicit
Private sheetUI As Sheet1
Private WithEvents sheet As Worksheet
Private Sub Class_Initialize()
Set sheet = Sheet1
Set sheetUI = Sheet1
End Sub
Private Sub sheet_Change(ByVal Target As Range)
If Intersect(Target, sheetUI.SomeSpecificRange) Then
'...
End If
End Sub
因此,代理类可以很好地处理工作表事件,而无需整个适配器管道。Public
它还可以通过其公开的成员处理来自演示者的命令。
但是代理类又名“抽象工作表”不是响应事件的正确位置:它是需要运行节目的演示者。
因此,您让代理触发一个事件以响应工作表事件,将消息打包并转发给演示者:
Option Explicit
Public Event SomeSpecificRangeChanged()
Private sheetUI As Sheet1
Private WithEvents sheet As Worksheet
Private Sub Class_Initialize()
Set sheet = Sheet1
Set sheetUI = Sheet1
End Sub
Private Sub sheet_Change(ByVal Target As Range)
If Intersect(Target, sheetUI.SomeSpecificRange) Then
RaiseEvent SomeSpecificRangeChanged
End If
End Sub
然后演示者可以处理SomeSpecificRangeChanged
代理类 - 调出一些用户表单,启动一些数据库查询,无论要求是什么:
Private WithEvents proxy As Sheet1Proxy
Private Sub Class_Initialize()
Set proxy = New Sheet1Proxy
End Sub
Private Sub proxy_SomeSpecificRangeChanged()
'business logic to run when SomeSpecificRange is changed
End Sub
问题是代理类与工作表耦合,现在演示者与代理耦合:我们已经抽象了很多东西,但是仍然没有办法将工作表/代理依赖换成其他东西并测试演示者逻辑而不涉及工作表。
所以我们创建了一个接口来将演示者与代理解耦 - 比如说,ISheet1Proxy
......现在我们被卡住了,因为我们无法在接口上公开事件。
这就是适配器模式发挥作用的地方,它允许我们为“命令”(演示者 -> 视图)和“事件”(视图 -> 演示者)的接口形式化。
使用适配器,工作表/代理和演示者现在完全解耦,现在您可以实现演示者逻辑,而无需任何知识Excel.Worksheet
,理想情况下是任何Excel.Range
或Excel.*
:每个工作表交互都被形式化为一些“命令”,发送到视图/工作表/代理,或发送给演示者的一些“事件”,就像在战舰项目中一样。
旁注,我发现WeakReference
正确拆除对象层次结构并不总是需要这些东西:这就是为什么在当前版本的战舰代码中不再使用它的原因。
显然,这是很多工作。对于 OOP 原则和学习编写可进行单元测试的解耦代码,这是一个很好的实践……但对于一个小型 VBA 项目来说,这对于 IMO 来说太过分了。
所有这些都将Excel.*
类视为具体类型,就 VBA 而言,也可能是这种情况。然而,Excel
就 .NET 而言,互操作类型都是接口,因此 Rubberduck 将通过为广受欢迎的 .NET 模拟框架Moq提供包装 API来极大地简化一切:
这将消除将工作表与用户代码完全分离以使其完全可测试的需要 - 唯一的要求是依赖注入,即更喜欢这个:
Public Sub DoSomething(ByVal target As Range)
target.Value = 42
End Sub
在此:
Public Sub DoSomething()
Dim target As Range
Set target = Sheet1.Range("A1")
target.Value = 42
End Sub