多态性

多态性是为不同的底层实现提供相同接口的能力

实现接口的能力允许完全将应用程序逻辑与 UI 或数据库或此工作表或该工作表分离。

假设你有一个表单本身实现的 ISomeView 接口:

Option Explicit

Public Property Get IsCancelled() As Boolean
End Property

Public Property Get Model() As ISomeModel
End Property

Public Property Set Model(ByVal value As ISomeModel)
End Property

Public Sub Show()
End Sub

表单的代码隐藏可能如下所示:

Option Explicit 
Implements ISomeView

Private Type TView
    IsCancelled As Boolean
    Model As ISomeModel
End Type
Private this As TView

Private Property Get ISomeView_IsCancelled() As Boolean
    ISomeView_IsCancelled = this.IsCancelled
End Property

Private Property Get ISomeView_Model() As ISomeModel
    Set ISomeView_Model = this.Model
End Property

Private Property Set ISomeView_Model(ByVal value As ISomeModel)
    Set this.Model = value
End Property

Private Sub ISomeView_Show()
    Me.Show vbModal
End Sub

Private Sub SomeOtherSettingInput_Change()
    this.Model.SomeOtherSetting = CBool(SomeOtherSettingInput.Value)
End Sub

'...other event handlers...

Private Sub OkButton_Click()
    Me.Hide
End Sub

Private Sub CancelButton_Click()
    this.IsCancelled = True
    Me.Hide
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CloseMode = VbQueryClose.vbFormControlMenu Then
        Cancel = True
        this.IsCancelled = True
        Me.Hide
    End If
End Sub

但是,没有什么禁止创建另一个实现 ISomeView 接口的类模块而不是用户表单 - 这可能是一个 SomeViewMock 类:

Option Explicit
Implements ISomeView

Private Type TView
    IsCancelled As Boolean
    Model As ISomeModel
End Type
Private this As TView

Public Property Get IsCancelled() As Boolean
    IsCancelled = this.IsCancelled
End Property

Public Property Let IsCancelled(ByVal value As Boolean)
    this.IsCancelled = value
End Property

Private Property Get ISomeView_IsCancelled() As Boolean
    ISomeView_IsCancelled = this.IsCancelled
End Property

Private Property Get ISomeView_Model() As ISomeModel
    Set ISomeView_Model = this.Model
End Property

Private Property Set ISomeView_Model(ByVal value As ISomeModel)
    Set this.Model = value
End Property

Private Sub ISomeView_Show()
    'do nothing
End Sub

现在我们可以更改使用 UserForm 的代码并使其在 ISomeView 接口上工作,例如通过将表单作为参数提供而不是实例化它:

Public Sub DoSomething(ByVal view As ISomeView)
    With view
        Set .Model = CreateViewModel
        .Show
        If .IsCancelled Then Exit Sub
        ProcessUserData .Model
    End With
End Sub

因为 DoSomething 方法取决于的接口(即,上抽象 ),而不是一个具体的类 (例如一个特定的 UserForm),我们可以写出一个自动化单元测试,以确保当 view.IsCancelledTrue ProcessUserData 不执行,通过使我们的测试创建一个 SomeViewMock 实例,将其 IsCancelled 属性设置为 True,并将其传递给 DoSomething

可测试代码取决于抽象

可以在 VBA 中编写单元测试,有些插件甚至可以将它集成到 IDE 中。但是当代码与工作表,数据库,表单或文件系统紧密耦合时,单元测试开始需要实际的工作表,数据库,表单或文件系统 - 而这些依赖性是新的失控失败这点可测试的代码应隔离,从而使单元测试并不需要一个实际的工作表,数据库,表格或文件系统。

通过编写针对接口的代码,以允许测试代码注入存根/模拟实现的方式(如上面的 SomeViewMock 示例),你可以在受控环境中编写测试,并模拟 42 个中的每一个可能发生的情况。表单数据上的用户交互的排列,甚至没有一次显示表单并手动单击表单控件。