你正在抽象Worksheet
在“代理”类后面;根据定义,它与工作表耦合,您想要的是确保抽象是密封的,以免您看到有漏洞的抽象并最终耦合其他代码与Excel.Worksheet
类型,这违背了整个目的。
对于项目的其余部分,工作表代理类充当facade操纵并理解有关特定对象的所有知识Excel.Worksheet
:这样做的结果是您现在可以使用两个模块来抽象工作表事物 - 工作表本身和代理类:
- 工作表代码隐藏可以抽象诸如
ListObject
/tables、命名范围等;使用Property Get
代理可以使用的成员。
- 工作表代理类从其余代码中抽象出工作表操作。
事实上,这种方法并没有为实际工作表代码隐藏留下太多空间/需要:我将开始对代理类中的所有内容进行编码,如果该模块变得太冗长,或者如果我发现它的抽象级别需要获得一个高一点,然后我会将较低级别的内容移至工作表本身的代码隐藏中。
Workheet
模块和其他文档模块不应实现接口 - 让工作表实现接口是混淆 VBA 并使 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
or Excel.*
:每个工作表交互都被形式化为发送到视图/工作表/代理的某些“命令”,或发送到演示者的某些“事件”,就像在战舰项目中一样。
旁注,我发现WeakReference
正确拆除对象层次结构并不总是需要这些东西:这就是为什么它在当前版本的战舰代码中不再使用。
显然这是a lot工作的。这是 OOP 原则的绝佳实践,也是学习编写可进行单元测试的解耦代码的绝佳实践……但对于小型 VBA 项目来说,在我看来,这太过分了。
这一切对待Excel.*
类作为具体类型,就 VBA 而言,情况也可能如此。但是,那Excel
就.NET而言,互操作类型都是接口,所以Rubberduck是即将极大地简化一切 https://github.com/rubberduck-vba/Rubberduck/pull/4681,通过提供包装 APIMoq https://github.com/moq/moq,一个广受欢迎的 .NET 模拟框架:
这将消除将工作表与用户代码完全解耦的需要,以便使其完全可测试 - 唯一的要求是依赖注入,即更喜欢这个:
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