VBA 工厂 VB_PredeclaredId = True 与 "anonymous" 实例优缺点

VBA Factory with VB_PredeclaredId = True vs "anonymous" instance pros and cons

好奇 VB_PredeclaredId = True 的工厂和 VBA 中的 "anonymous" 实例之间的 advantages/disadvantages 是什么。每个都有自己的场景或更好地坚持其中一个,为什么?任何我可以阅读更多相关信息的反馈或链接将不胜感激!谢谢!

从 A1 开始的工作表数据

Name    Manager Target
Person1 Manager1    97
Person2 Manager2    92
Person3 Manager3    95
Person4 Manager4    105
Person5 Manager5    108
Person6 Manager6    88

工厂 - Class

'@Folder("VBAProject")

Option Explicit
'@PredeclaredId

Public Function getPerson(ByVal name As String, ByVal manager As String, ByVal target As Double) As Person
    With New Person
        .name = name
        .manager = manager
        .Targer = target
        Set getPerson = .self
    End With
End Function

人 - Class

Private pName As String
Private pManager As String
Private pTarger As Double

Public Property Get Targer() As Double
    Targer = pTarger
End Property

Public Property Let Targer(ByVal value As Double)
    pTarger = value
End Property


Public Property Get manager() As String
    manager = pManager
End Property

Public Property Let manager(ByVal value As String)
    pManager = value
End Property


Public Property Get name() As String
    name = pName
End Property

Public Property Let name(ByVal value As String)
    pName = value
End Property

Public Function toString() As String
    toString = "Name: " & Me.name & ", Manager: " & Me.manager & ", Targer: " & Me.Targer
End Function

Public Function self() As Person
    Set self = Me
End Function

测试 - 模块

Sub test()

Dim i As Long
For i = 2 To 6

    With New Person
        .name = Range("A" & i)
        .manager = Range("b" & i)
        .Targer = Range("c" & i)
        Debug.Print .toString
    End With

    Debug.Print Factory.getPerson(name:=Range("A" & i), _
                manager:=Range("B" & i), target:=Range("C" & i)).toString

    'or shorter whithout feild names
    Debug.Print Factory.getPerson(Range("A" & i), Range("B" & i), Range("C" & i)).toString

Next i

End Sub

TL;DR: 真的是苹果和香蕉。匿名对象是一种语言结构;工厂没有在语言规范中定义,它们更像是一种 设计模式 ,是的,使用每种不同的方法有不同的原因 - 尽管 IMO "factory bag module"是个坏主意。


匿名对象

匿名对象很棒 - 您可以调用 With 块持有的对象的成员,而无需添加局部变量。那是 With New,但它对 With CreateObject:

也特别有用
With CreateObject("Scripting.FileSystemObject")
    '...
End With

它们创建对象,但这些对象(通常 - .Self getter 可以阻止它)被限制在包含 With 的块中;匿名对象对于您此时此地需要的对象很有用,超过那个点就不再需要了。它们基本上是语言功能

工厂Class(或模块)

我们将离开 语言特性 领域,进入 设计模式 领域。现在我们正在谈论将给定对象的相对复杂的创建封装在专用于该目的的 class 中。

VB_PredeclaredId = True 属性(无论是否由 @PredeclaredId Rubberduck 注释设置)使 class 像任何其他标准模块一样工作。

Factory class 公开了 getPerson(或 CreatePerson)方法的问题是,您现在有借口用一些 getAnotherThing(或 CreateAnotherThing),不知不觉中,您看到的是一个 class/module,它几乎可以创造任何东西,无论这些东西是否相关。

肯定有更好的方法。

工厂方法

如果 Person 对象知道如何创建 Person class 的新实例会怎么样?

Set p = Person.Create(1188513, "Mathieu")

这需要 Person class 上的 VB_PredeclaredId = True 属性和 Person 默认接口 才能公开要从该默认实例调用的 Create 方法。

问题是,现在任何使用 Person 对象的东西现在都会看到一个令人困惑的 API,它公开了 IDNameProperty Let 成员属性,以及一个仅用于默认实例的 Create 函数。

这个问题有一个解决方案:唯一 "allowed" 与 Person 一起工作的代码应该是负责调用该类型的 Create 工厂方法的代码。其他一切都只会看到一个 IPerson - Person 实现的一个显式接口,用于定义其余代码应如何与该类型的对象交互......而 IPerson 不需要公开任何 Property Let 成员,或 Create 方法。

还有一个问题该模式没有解决 - 假设 IPerson 由两个或多个 class 实现(例如,EmployeeManager) : 即使整个项目只看到 IPerson,仍然有一些代码在调用 Employee.CreateManager.Create,并且这些代码本质上是 耦合的 IPerson 接口的这些特定实现。这是一个问题,因为这种耦合首先从根本上否定了针对接口进行编码的好处。

抽象工厂

为了将创建 IPerson 对象的代码与 EmployeeManager classes 分离,我们可以创建一个抽象的 IPersonFactory 接口工厂方法本身:现在使用工厂和创建 IPerson 对象的代码甚至不知道它正在创建什么具体类型的对象,解耦已经完成。

你可能不需要那种级别的解耦,除非你已经用一套完整的单元测试覆盖了所有内容,但无论如何知道它的存在是很有用的。

所以你会有 EmployeeFactoryManagerFactory classes,它们都实现了 IPersonFactory 并为一些 Create 函数提供了一个实现 returns 一个 IPerson 对象。

...老实说,这就是 person/employee 示例有点崩溃的地方,因为这样的 class 更像是一个简单的 DTO(数据传输对象 - 即一堆 public 字段,或 read/write 属性)而不是任何实际有责任的东西 - 所以让我们放弃它。

我们将有一个 SqlConnectionFactory、一个 OracleConnectionFactory 和一个 MySqlConnectionFactory,所有这些都实现一些 IDbConnectionFactory 以产生一些封装的 IDbConnection 对象,你会猜到,一个数据库连接。

所以我们可以得到如下代码:

Public Sub DoSomething(ByVal factory As IDbConnectionFactory)
    Dim pwd As String
    pwd = InputBox("SA Password?") 'bad example: now we're coupled with the InputBox API!

    Dim conn As IDbConnection
    Set conn = factory.Create("server name", "sa", pwd)

    '...
End Sub

DoSomething 方法可以针对 SQL 服务器、Oracle 或 MySQL 数据库服务器执行其操作,并且永远不需要关心是哪个一个它实际上与 .

一起工作

Abstract Factory 在您注入依赖项(c.f。"dependency injection")时非常有用,这些依赖项本身具有直到最后一刻才能解决的依赖项,例如,因为您需要一些用户需要通过一些 UI 提供的参数(理想情况下,它本身也被抽象在接口后面,这样您就可以在不弹出 UI 的情况下对方法进行单元测试 -虽然,Rubberduck 的 Fakes API 确实允许您劫持一个 InputBox... 假设我们正在弹出一个 UserForm