在“代理”接口+类

时间:2019-04-15 12:28:44

标签: excel vba rubberduck

我正在尝试进一步建立在this excellent example的基础上,proxy classes已实现了这些非常有见地的RubberduckVBA.com文章中讨论的最佳实践:

  1. 通过Utilizing the UserForm control从Excel工作簿/工作表中抽象出来;
  2. Adding "Apply" logic to #2,而不会弄乱其默认实例的状态; <​​/ li>
  3. existing example

我想向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中如何实现事件处理,但是其设计在一些重要方面有所不同:

  1. “战列舰”遵循MVC设计模式,而我想像示例一样坚持使用MVP;
  2. “战舰”通过“查看”类从其工作表中抽象出来,而我希望每个工作表都有一个单独的代理接口+类;
  3. “战斗”部署了base project,而我很高兴将视图和表单代理实现与演示者结合使用(如果可能的话,可以处理事件)。

考虑到这一点,我绝对希望看到一个代码示例,该示例将我上面描述的“ Worksheet_Change”事件添加到Worksheet proxy approach中,该事件已经实现了Workbook和Worksheet代理并遵循MVP模式。

即使没有代码示例,如果我将这些问题弄清楚也会有很大帮助:

  1. Adapter Pattern是否指示后面应该绝对为零工作表代码?如果我像这样在Sheet2(而不是其代理服务器)内开始“ Worksheet_Change”事件实现,将会朝错误的方向迈出一步:
Public Event SheetChanged(ByVal changedRange As Range)

Private Sub Worksheet_Change(ByVal Target As Range)
    RaiseEvent SheetChanged(Target)
End Sub
  1. 如果并非绝对需要使用Lazy Object/Weak Reference进行事件处理,是否最好使用“ IViewCommands”和“ IViewEvents”接口列出从Presenter发送到View的所有命令,以及从View引发并分别发送到Presenter的事件?
  2. 我认为我需要使用{{3}}才能公开事件。如果是这样,并假设我无需适配器就可以完成工作(请参阅上面的#2),这是否意味着我的“ Sheet2Proxy”类将必须通过其“ IViewEvents”持有对Presenter的弱引用(再次参见# 2以上)界面?

1 个答案:

答案 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>

Rubberduck mocking framework coming soon-ish

这将消除将工作表与用户代码完全脱钩以便使其完全可测试的需要-唯一的要求是依赖注入,即更喜欢此:

Excel.*

对此:

Excel