将parameter passing给VBA中的构造函数

你怎么能构造对象直接传递给你自己的类?

像这样的东西:

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

不能做到这一点非常烦人,而且最终会有肮脏的解决scheme来解决这个问题。

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

1.-在你的每个自定义类中实现一个公共启动子程序。 我在我所有的课程中都将其称为InitiateProperties。 这个方法必须接受你想发送给构造函数的参数。

2.-创build一个名为factory的模块,并创build一个名为“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所指出的那样,您可以通过避免在构造函数中创build一个局部variables来用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 

哪个更好

我使用一个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 

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

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

要么:

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

它很干净:工厂做得很less,并且它在所有对象中都一致,只是每个创build者创build一个Init调用。

它是相当面向对象的: Init函数是在对象内部定义的。

编辑

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

 NewLine.DeleteAllLinesShorterThan 10 

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

当你导出一个类模块,并在记事本中打开文件,你会注意到,靠近顶部,一堆隐藏的属性(VBE不显示它们,并没有公开function来调整大部分)。 其中之一是VB_PredeclaredId

 Attribute VB_PredeclaredId = False 

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

或者,如果你使用的是Rubberduck ,你可以在模块的顶部指定一个特殊的注释:

 '@PredeclaredId Option Explicit 

Rubberduck发出一个检查结果,表示模块的属性与模块注释不同步,只需点击一下鼠标,就可以在不离开VBE的情况下设置VB_PredeclaredId属性(注意:截至本文的写作仍然是一个实验性function)。

PredeclaredId类有一个“全局实例”,你可以免费得到 – 就像UserForm模块(导出一个用户表单,你会看到它的predeclaredId属性设置为true)。

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

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

[ Employee类]

 '@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 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正在处理默认实例 ,即它被认为是types的成员,并且仅从默认实例中调用。

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

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

而这很糟糕,因为现在你有一个令人困惑的API。 您可以使用'@Description annotations / VB_Description '@Description属性来logging使用情况,但是如果没有使用'@Description ,那么在编辑器中没有任何东西可以显示呼叫站点的信息。

此外, Property Let成员是可访问的,所以你的Employee实例是可变的

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

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

[ IEmployee课堂]

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

现在你让Employee 实现 IEmployee – 最终的类可能是这样的:

[ Employee类]

 '@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 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) 

而且,由于客户端代码是针对接口编写的,所以唯一的成员empl公开的是由IEmployee接口定义的成员,这意味着它不会看到Create方法, Self getter或Property Let mutators:so而不是使用“具体的” Employee类,其余的代码可以使用“抽象的” IEmployee接口,并享受一个不可变的多态对象。

另一种方法

假设你创build一个类clsBitcoinPublicKey

在类模块中创build一个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(...) 

唯一的惩罚是额外的调用,但优点是你可以把所有的东西放在类模块中,debugging变得更容易。