我正在尝试进一步建立在this excellent example的基础上,proxy classes已实现了这些非常有见地的RubberduckVBA.com文章中讨论的最佳实践:
我想向given example添加一个事件处理程序,该事件处理程序(为简单起见)报告Sheet1的“ A1”单元格中Sheet2的“更改”范围的左上单元格的值以及时间“ A2”中更改的内容。我通常会像这样在Sheet2的代码中进行设置:
Private Sub Worksheet_Change(ByVal Target As Range)
Sheet1.Cells(1, 1).Value2 = Target.Cells(1, 1).Value2
Sheet1.Cells(1, 2).Value2 = CStr(Now)
End Sub
但是我想知道如何在Battleship tutorial中最好地实现这一点,考虑到它是围绕MVP模式设计的,并通过代理接口利用了工作簿和工作表抽象,这意味着期望零/最小工作表代码。 >
我能够理解在很棒的Adapter Pattern中如何实现事件处理,但是其设计在一些重要方面有所不同:
考虑到这一点,我绝对希望看到一个代码示例,该示例将我上面描述的“ Worksheet_Change”事件添加到Worksheet proxy approach中,该事件已经实现了Workbook和Worksheet代理并遵循MVP模式。
即使没有代码示例,如果我将这些问题弄清楚也会有很大帮助:
Public Event SheetChanged(ByVal changedRange As Range)
Private Sub Worksheet_Change(ByVal Target As Range)
RaiseEvent SheetChanged(Target)
End Sub
答案 0 :(得分:2)
您正在将Worksheet
抽象到“代理”类的后面;根据定义,它与工作表结合在一起,而您想要的是确保抽象是气密的,以免您看到的是泄漏的抽象,并最终将其他代码与{{ 1}}类型,这会破坏整个目的。
在项目的其余部分中,工作表代理类充当 facade 的角色,它操纵并理解有关特定Excel.Worksheet
的所有信息:您现在可以使用两个模块来抽象工作表内容-工作表本身和代理类:
Excel.Worksheet
/表,命名范围等内容;使用代理可以使用的ListObject
个成员。实际上,这种方法对于实际的工作表代码隐藏并没有太多的余地/需要:我将开始对代理类中的所有内容进行编码,并且如果该模块过于冗长,或者是否正在寻找其抽象级别的需求要提高一点,然后将较低层的内容移到工作表的代码本身。
Property Get
模块和其他文档模块不应实现接口-使工作表实现接口是混淆和使VBA崩溃的一种好方法:不要这样做。因此,这可能是您的代码背后:
Workheet
然后代理类可以做到这一点:
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
-提出一些UserForm,激发一些数据库查询,无论要求如何:
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
或{{1 }}:每个工作表的交互都被规范化为发送给视图/工作表/代理的一些“命令”,或者发送给演示者的一些“事件”,就像在Battleship项目中一样。
我注意到,Excel.Range
东西并不需要总是能正确拆除对象层次结构:这就是为什么在当前版本的《战舰》代码中不再使用它。
显然,这是很多的工作。对于OOP原则和学习编写可以进行单元测试的解耦代码来说,这是一个很好的实践……但是对于一个小型VBA项目,这绝对是对IMO的过度杀伤。
所有这一切都将Excel.*
类视为具体类型,就VBA而言,也是如此。但是,就.NET而言,WeakReference
互操作类型都是所有接口,因此,Rubberduck是about to tremendously simplify everything,它为Moq(一种非常流行的.NET模拟框架)提供了包装API: / p>
这将消除将工作表与用户代码完全脱钩以便使其完全可测试的需要-唯一的要求是依赖注入,即更喜欢此:
Excel.*
对此:
Excel