在VBA中将参数传递给Constructor

时间:2013-03-05 12:43:24

标签: vba class oop constructor factory

如何构造将参数直接传递给自己的类的对象?

这样的事情:

Dim this_employee as Employee
Set this_employee = new Employee(name:="Johnny", age:=69)

无法做到这一点非常烦人,你最终会得到一些肮脏的解决方案来解决这个问题。

6 个答案:

答案 0 :(得分:104)

这是我最近使用的一个小技巧并带来了好的结果。我想与那些经常与VBA打架的人分享。

1 .- 在每个自定义类中实现公共启动子例程。我在所有课程中称它为InitiateProperties。此方法必须接受您要发送给构造函数的参数。

2 .- 创建一个名为factory的模块,并创建一个公共函数,其单词“Create”加上与该类相同的名称,以及与构造函数需要的相同的传入参数。此函数必须实例化您的类,并调用点(1)中解释的启动子例程,传递接收的参数。最后返回实例化和启动的方法。

示例:

假设我们有自定义类Employee。如前面的示例所示,必须使用名称和年龄进行实例化。

这是InitiateProperties方法。 m_name和m_age是我们要设置的私有属性。

Public Sub InitiateProperties(name as String, age as Integer)

    m_name = name
    m_age = age

End Sub

现在在工厂模块中:

Public Function CreateEmployee(name as String, age as Integer) as Employee

    Dim employee_obj As Employee
    Set employee_obj = new Employee

    employee_obj.InitiateProperties name:=name, age:=age
    set CreateEmployee = employee_obj

End Function

最后当你想要实例化员工时

Dim this_employee as Employee
Set this_employee = factory.CreateEmployee(name:="Johnny", age:=89)

当你有几个课时特别有用。只需在模块工厂中为每个函数放置一个函数,并通过调用 factory.CreateClassA(arguments) factory.CreateClassB(other_arguments)等实例化。

修改

正如stenci指出的那样,你可以通过避免在构造函数中创建一个局部变量来使用terser语法做同样的事情。例如,CreateEmployee函数可以这样写:

Public Function CreateEmployee(name as String, age as Integer) as Employee

    Set CreateEmployee = new Employee
    CreateEmployee.InitiateProperties name:=name, age:=age

End Function

哪个更好。

答案 1 :(得分:31)

我使用一个Factory模块,每个类包含一个(或多个)构造函数,调用每个类的Init成员。

例如Point类:

Class Point
Private X, Y
Sub Init(X, Y)
  Me.X = X
  Me.Y = Y
End Sub

Line

Class Line
Private P1, P2
Sub Init(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
  If P1 Is Nothing Then
    Set Me.P1 = NewPoint(X1, Y1)
    Set Me.P2 = NewPoint(X2, Y2)
  Else
    Set Me.P1 = P1
    Set Me.P2 = P2
  End If
End Sub

Factory模块:

Module Factory
Function NewPoint(X, Y)
  Set NewPoint = New Point
  NewPoint.Init X, Y
End Function

Function NewLine(Optional P1, Optional P2, Optional X1, Optional X2, Optional Y1, Optional Y2)
  Set NewLine = New Line
  NewLine.Init P1, P2, X1, Y1, X2, Y2
End Function

Function NewLinePt(P1, P2)
  Set NewLinePt = New Line
  NewLinePt.Init P1:=P1, P2:=P2
End Function

Function NewLineXY(X1, Y1, X2, Y2)
  Set NewLineXY = New Line
  NewLineXY.Init X1:=X1, Y1:=Y1, X2:=X2, Y2:=Y2
End Function

这种方法的一个很好的方面是可以很容易地在表达式中使用工厂函数。例如,可以执行以下操作:

D = Distance(NewPoint(10, 10), NewPoint(20, 20)

或:

D = NewPoint(10, 10).Distance(NewPoint(20, 20))

它很干净:工厂做得很少,它在所有对象上都是一致的,只是创建和每个创建者上的Init调用。

它非常面向对象:Init函数在对象内定义。

修改

我忘了补充说这允许我创建静态方法。例如,我可以做一些事情(在使参数可选后):

NewLine.DeleteAllLinesShorterThan 10

不幸的是每次都会创建一个新的对象实例,因此执行后任何静态变量都会丢失。必须在模块中定义此伪静态方法中使用的行集合和任何其他静态变量。

答案 2 :(得分:17)

导出类模块并在记事本中打开文件时,您会注意到顶部附近有一堆隐藏属性(VBE不会显示它们,也不会显示它们调整其中大部分的功能)。其中之一是VB_PredeclaredId

Attribute VB_PredeclaredId = False

将其设置为True,将模块保存并重新导入VBA项目。

具有PredeclaredId的类具有"全局实例"您可以免费获得 - 与UserForm模块完全相同(导出用户表单,您将看到其predeclaredId属性设置为true)。

很多人只是愉快地使用预先声明的实例来存储状态。那是错的 - 就像在静态类中存储实例状态一样!

相反,您可以利用该默认实例来实现工厂方法:

[Employee class]

'@PredeclaredId
Option Explicit

Private Type TEmployee
    Name As String
    Age As Integer
End Type

Private this As TEmployee

Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As Employee
    With New Employee
        .Name = emplName
        .Age = emplAge
        Set Create = .Self 'returns the newly created instance
    End With
End Function

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

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

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

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

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

有了这个,你可以这样做:

Dim empl As Employee
Set empl = Employee.Create("Johnny", 69)

Employee.Create正在处理默认实例,即它被视为类型的成员,并且仅从默认实例调用。

问题是,这也是完全合法的:

Dim emplFactory As New Employee
Dim empl As Employee
Set empl = emplFactory.Create("Johnny", 69)

这很糟糕,因为现在你有一个令人困惑的API。您可以使用'@Description注释/ VB_Description属性来记录使用情况,但如果没有Rubberduck,编辑器中的任何内容都不会在呼叫网站上显示该信息。

此外,Property Let成员可以访问,因此您的Employee实例可变

empl.Name = "Booba" ' Johnny no more!

诀窍是让你的类实现一个接口,它只暴露需要暴露的内容:

[IEmployee class]

Option Explicit

Public Property Get Name() As String : End Property
Public Property Get Age() As Integer : End Property

现在你让Employee 实现 IEmployee - 最终的类可能如下所示:

[Employee class]

'@PredeclaredId
Option Explicit
Implements IEmployee

Private Type TEmployee
    Name As String
    Age As Integer
End Type

Private this As TEmployee

Public Function Create(ByVal emplName As String, ByVal emplAge As Integer) As IEmployee
    With New Employee
        .Name = emplName
        .Age = emplAge
        Set Create = .Self 'returns the newly created instance
    End With
End Function

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

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

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

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

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

Private Property Get IEmployee_Name() As String
    IEmployee_Name = Name
End Property

Private Property Get IEmployee_Age() As Integer
    IEmployee_Age = Age
End Property

请注意,Create方法现在返回接口,接口不会公开Property Let成员?现在调用代码可能如下所示:

Dim empl As IEmployee
Set empl = Employee.Create("Immutable", 42)

由于客户端代码是针对接口编写的,因此公开的唯一成员emplIEmployee接口定义的成员,这意味着它没有看到{{1} }方法,也不是Create getter,也不是任何Self mutator:所以不要使用"具体" Property Let类,其余代码可以使用" abstract" Employee接口,享受不可变的多态对象。

答案 3 :(得分:1)

使用技巧

Attribute VB_PredeclaredId = True

我发现了另一种更紧凑的方法:

Option Explicit
Option Base 0
Option Compare Binary

Private v_cBox As ComboBox

'
' Class creaor
Public Function New_(ByRef cBox As ComboBox) As ComboBoxExt_c
  If Me Is ComboBoxExt_c Then
    Set New_ = New ComboBoxExt_c
    Call New_.New_(cBox)
  Else
    Set v_cBox = cBox
  End If
End Function

如您所见,调用New_构造函数可以创建和设置类的私有成员(如init),唯一的问题是,如果在非静态实例上调用它将重新初始化私有成员。但这可以通过设置一个标志来避免。

答案 4 :(得分:-1)

另一种方法

假设您创建了一个类clsBitcoinPublicKey

在类模块中创建一个ADDITIONAL子例程,该子例程的行为与您希望真实构造函数的行为一样。下面我将其命名为ConstructorAdjunct。

Public Sub ConstructorAdjunct(ByVal ...)

 ...

End Sub

From the calling module, you use an additional statement

Dim loPublicKey AS clsBitcoinPublicKey

Set loPublicKey = New clsBitcoinPublicKey

Call loPublicKey.ConstructorAdjunct(...)

唯一的惩罚是额外的调用,但优点是你可以保留类模块中的所有内容,并且调试变得更容易。

答案 5 :(得分:-2)

为什么不这样:

  1. 在类模块»myClass«中,使用Public Sub Init(myArguments)代替Private Sub Class_Initialize()
  2. 实例化: Dim myInstance As New myClass: myInstance.Init myArguments