封装

封装隐藏了客户端代码的实现细节

处理将对 QueryClose 示例演示封装:形式都有一个复选框控制,但它的客户端代码不直接合作 -复选框是一个实现细节,什么客户端代码需要知道的是设置是否启用。

当复选框值更改时,处理程序将分配一个私有字段成员:

Private Type TView
    IsCancelled As Boolean
    SomeOtherSetting As Boolean
    'other properties skipped for brievety
End Type
Private this As TView

'...

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

当客户端代码想要读取该值时,它不需要担心复选框 - 而只是使用 SomeOtherSetting 属性:

Public Property Get `SomeOtherSetting()` As Boolean
    SomeOtherSetting = this.SomeOtherSetting
End Property

SomeOtherSetting 属性封装了复选框’state; 客户端代码不需要知道有一个复选框,只是有一个布尔值的设置。通过封装 Boolean 值,我们在复选框周围添加了一个抽象层

使用接口来强制实现不变性

让我们通过表单的模型**封装在专用的类模块中来进一步推进。但是如果我们为 UserNameTimestamp 制作了一个 Public Property,我们将不得不暴露 Property Let 访问器,使属性变得可变,并且我们不希望客户端代码能够在设置后更改这些值。

抽象示例中的 CreateViewModel 函数返回一个 ISomeModel 类:这是我们的接口,它看起来像这样:

Option Explicit

Public Property Get Timestamp() As Date
End Property

Public Property Get UserName() As String
End Property

Public Property Get AvailableItems() As Variant
End Property

Public Property Let AvailableItems(ByRef value As Variant)
End Property

Public Property Get SomeSetting() As String
End Property

Public Property Let SomeSetting(ByVal value As String)
End Property

Public Property Get SomeOtherSetting() As Boolean
End Property

Public Property Let SomeOtherSetting(ByVal value As Boolean)
End Property

请注意,TimestampUserName 属性仅显示 Property Get 访问器。现在 SomeModel 类可以实现该接口:

Option Explicit
Implements ISomeModel

Private Type TModel
    Timestamp As Date
    UserName As String
    SomeSetting As String
    SomeOtherSetting As Boolean
    AvailableItems As Variant
End Type
Private this As TModel

Private Property Get ISomeModel_Timestamp() As Date
    ISomeModel_Timestamp = this.Timestamp
End Property

Private Property Get ISomeModel_UserName() As String
    ISomeModel_UserName = this.UserName
End Property

Private Property Get ISomeModel_AvailableItems() As Variant
    ISomeModel_AvailableItems = this.AvailableItems
End Property

Private Property Let ISomeModel_AvailableItems(ByRef value As Variant)
    this.AvailableItems = value
End Property

Private Property Get ISomeModel_SomeSetting() As String
    ISomeModel_SomeSetting = this.SomeSetting
End Property

Private Property Let ISomeModel_SomeSetting(ByVal value As String)
    this.SomeSetting = value
End Property

Private Property Get ISomeModel_SomeOtherSetting() As Boolean
    ISomeModel_SomeOtherSetting = this.SomeOtherSetting
End Property

Private Property Let ISomeModel_SomeOtherSetting(ByVal value As Boolean)
    this.SomeOtherSetting = value
End Property

Public Property Get Timestamp() As Date
    Timestamp = this.Timestamp
End Property

Public Property Let Timestamp(ByVal value As Date)
    this.Timestamp = value
End Property

Public Property Get UserName() As String
    UserName = this.UserName
End Property

Public Property Let UserName(ByVal value As String)
    this.UserName = value
End Property

Public Property Get AvailableItems() As Variant
    AvailableItems = this.AvailableItems
End Property

Public Property Let AvailableItems(ByRef value As Variant)
    this.AvailableItems = value
End Property

Public Property Get SomeSetting() As String
    SomeSetting = this.SomeSetting
End Property

Public Property Let SomeSetting(ByVal value As String)
    this.SomeSetting = value
End Property

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

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

接口成员都是 Private,并且必须实现接口的所有成员才能编译代码。Public 成员不是接口的一部分,因此不会暴露于针对 ISomeModel 接口编写的代码。

使用工厂方法模拟构造函数

使用 VB_PredeclaredId 属性,我们可以使 SomeModel 类有一个默认实例,并编写一个函数,类似于类型级(VB.NET 中的 Shared,C#中的 static)成员,客户端代码可以调用而无需首先创建像我们在这里做的一个实例:

Private Function `CreateViewModel()` As ISomeModel
    Dim result As ISomeModel
    Set result = SomeModel.Create(Now, Environ$("UserName"))
    result.AvailableItems = GetAvailableItems
    Set CreateViewModel = result
End Function

这个工厂方法分配从 ISomeModel 接口访问时只读的属性值,这里是 TimestampUserName

Public Function Create(ByVal pTimeStamp As Date, ByVal pUserName As String) As ISomeModel
    With New SomeModel
        .Timestamp = pTimeStamp
        .UserName = pUserName
        Set Create = .Self
    End With
End Function

Public Property Get Self() As ISomeModel
    Set Self = Me
End Property

现在我们可以对 ISomeModel 接口进行编码,该接口将 TimestampUserName 作为只读属性公开,永远不能重新分配(只要代码是针对接口编写的)。